如果Redis的读写请求量很大,那么单个实例很有可能承担不了这么大的请求量,如何提高Redis的性能呢?你也许已经想到了,可以部署多个副本节点,业务采用读写分离的方式,把读请求分担到多个副本节点上,提高访问性能。要实现读写分离,就必须部署多个副本,每个副本需要实时同步主节点的数据。
这篇文章我们就来介绍一下Redis主从复制流程和原理,以及在复制过程中有可能产生的各种问题。
主要围绕着三个方面
Redis主从复制、哨兵机制、Redis集群
目标
- 理解什么是Redis的主从复制、哨兵机制、官方集群方案Cluster
- 会搭建Redis主从、哨兵、Redis Cluster
- 理解Redis主从存在的问题
- 理解Redis主从的实现原理(全量同步,增量同步,部分同步的概念)
- 理解Redis哨兵机制的实现原理
- 理解Cluster的实现原理
Redis主从复制
生产上永远不可能用单机,所有的节点,包括应用节点,中间件节点,数据节点等都要保证高可用,最基本的要求。
主从必然存在延迟问题
,Redis按照分布式集群来讲,就不是强一致性的CAP结构,只保证了高可用AP,没有保证强一致性CP,所以必然存在延迟。
CAP:
一致性(Consistency)
可用性(Availability)
分区容忍性(Partition tolerance)
如果业务需要主从机制必须强一致,那就要考虑适不适合选择Redis。
什么是主从复制
如果单机情况下,机器重启,内存数据丢失,如何保证数据的高可用呢?持久化方案
如果单机,这台机器的硬件坏了,例如硬盘坏了,或者访问压力太大,服务器崩溃了,如何保证数据的高可用呢?主从复制
Redis的主从机制:主负责读写,从一般只读不能写(客户端)。
和Mysql主从复制的原因一样,Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况。为了分担读压力,Redis支持主从复制,Redis的主从结构可以采用一主多从或者级联结构,Redis主从复制可以根据是否是全量分为全量同步和增量同步。下图为级联结构。 如下图:
主从复制特点:
- 1)采用异步复制;
- 2)一个主redis可以含有多个从redis;
- 3)每个从redis可以接收来自其他从redis服务器的连接;
- 4)主从复制对于主redis服务器来说是非阻塞的,这意味着当从服务器在进行主从复制同步过程中,主redis仍然可以处理外界的访问请求;
- 5)主从复制对于从redis服务器来说也是非阻塞的,这意味着,即使从redis在进行主从复制过程中也可以接受外界的查询请求,只不过这时候从redis返回的是以前老的数据,如果你不想这样,那么在启动redis时,可以在配置文件中进行设置,那么从redis在复制同步过程中来自外界的查询请求都会返回错误给客户端;(虽然说主从复制过程中对于从redis是非阻塞的,但是当从redis从主redis同步过来最新的数据后还需要将新数据加载到内存中,在加载到内存的过程中是阻塞的,在这段时间内的请求将会被阻,对于大数据集,加载到内存的时间也是比较多的);
- 6)主从复制提高了redis服务的扩展性,避免单个redis服务器的读写访问压力过大的问题,同时也可以给为数据备份及冗余提供一种解决方案;
- 7)为了避免主redis服务器写磁盘压力带来的开销,可以配置让主redis不在将数据持久化到磁盘,而是通过连接让一个配置的从redis服务器及时的将相关数据持久化到磁盘,不过这样会存在一个问题,就是主redis服务器一旦重启,因为主redis服务器数据为空,这时候通过主从同步可能导致从redis服务器上的数据也被清空;
主从配置
主Redis配置
- 无需特殊配置
从Redis配置
- 修改从服务器上的 redis.conf 文件:
slaveof <masterip> <masterport>
表示当前【从服务器】对应的【主服务器】的IP是192.168.10.135,端口是6379。
slaveof 192.168.10.135 6379 (5.0之前的配置)
replicaof 192.168.19.135 6379 (5.0之后的配置)
实现原理
一.复制整体流程
复制整体步骤如下:
- 从节点执行 slaveof 命令
- 从节点只是保存了 slaveof 命令中主节点的信息,并没有立即发起复制
- 从节点内部的定时任务发现有主节点的信息,开始使用 socket 连接主节点
- 连接建立成功后,发送 ping 命令,希望得到 pong 命令响应,否则会进行重连
- 如果主节点设置了权限,那么就需要进行权限验证;如果验证失败,复制终止。
- 权限验证通过后,进行数据同步,这是耗时最长的操作,主节点将把所有的数据全部发送给从节点。(
全量同步
) - 当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。接下来,主节点就会持续的把写命令发送给从节点,保证主从数据一致性(
增量同步
)。
二.数据间的同步
上面说的整体复制过程,其中有一个步骤是“同步数据集”,这个就是现在讲的‘数据间的同步’。
redis 同步有 2 个命令:
sync
和 psync
,前者是 redis 2.8 之前的同步命令,后者是 redis 2.8 为了优化 sync 新设计的命令。我们会重点关注 2.8 的 psync 命令。
psync 命令需要 3 个组件支持:
主从节点各自复制偏移量
主节点复制积压缓冲区
主节点运行 ID
主从节点各自复制偏移量:
- 参与复制的主从节点都会维护自身的复制偏移量。
- 主节点在处理完写入命令后,会把命令的字节长度做累加记录,统计信息在 info replication 中的 masterreploffset 指标中。
- 从节点每秒钟上报自身的的复制偏移量给主节点,因此主节点也会保存从节点的复制偏移量。
- 从节点在接收到主节点发送的命令后,也会累加自身的偏移量,统计信息在 info replication 中。
- 通过对比主从节点的复制偏移量,可以判断主从节点数据是否一致。
主节点复制积压缓冲区:
- 复制积压缓冲区是一个保存在主节点的一个固定长度的先进先出的队列。默认大小 1MB。
- 这个队列在 slave 连接时创建。这时主节点响应写命令时,不但会把命令发送给从节点,也会写入复制缓冲区。
- 他的作用就是
用于部分复制
和复制命令丢失的数据补救
。通过 info replication 可以看到相关信息。
主节点运行 ID:
-
每个 redis 启动的时候,都会生成一个 40 位的运行 ID。
-
运行 ID 的主要作用是用来识别 Redis 节点。如果使用 ip+port 的方式,那么如果主节点重启修改了 RDB/AOF 数据,从节点再基于偏移量进行复制将是不安全的。所以,当运行 id 变化后,从节点将进行全量复制。也就是说,redis 重启后,默认从节点会进行全量复制。
-
如何在重启时不改变运行 ID 呢?
-
- 可以通过 debug reload 命令重新加载 RDB 并保持运行 ID 不变。从而有效的避免不必要的全量复制。
- 他的缺点则是:debug reload 命令会阻塞当前 Redis 节点主线程,因此对于大数据量的主节点或者无法容忍阻塞的节点,需要谨慎使用。一般通过故障转移机制可以解决这个问题。
psync 命令的使用方式:
命令格式为 psync
runId:从节点所复制主节点的运行 id
offset:当前从节点已复制的数据偏移量
psync 执行流程:
- 1、客户端向服务器发送SLAVEOF命令,让当前服务器成为Slave;
- 2、当前服务器根据自己是否保存Master runid来判断是否是第一次复制,如果是第一次同步则跳转到3,否则跳转到4;
- 3、向Master发送PSYNC ? -1 命令来进行完整同步;
- 4、向Master发送PSYNC runid offset;
- 5、Master接收到PSYNC 命令后
首先判断runid是否和本机的id一致,如果一致则会再次判断offset偏移量和本机的偏移量相差有没有超过复制积压缓冲区大小,如果没有那么就给Slave发送CONTINUE,此时Slave只需要等待Master传回失去连接期间丢失的命令;
- 6、
如果runid和本机id不一致或者双方offset差距超过了复制积压缓冲区大小,那么就会返回FULLRESYNC runid offset,Slave将runid保存起来,并进行完整同步
。
流程说明:从节点发送 psync 命令给主节点,runId 就是目标主节点的 ID,如果没有默认为 -1,offset 是从节点保存的复制偏移量,如果是第一次复制则为 -1.
主节点会根据 runid 和 offset 决定返回结果:
- 如果回复 +FULLRESYNC ,那么从节点将触发全量复制流程。
- 如果回复 +CONTINUE,从节点将触发部分复制。
- 如果回复 +ERR,说明主节点不支持 2.8 的 psync 命令,将使用 sync 执行全量复制。
三.全量复制
全量复制是 Redis 最早支持的复制方式,也是主从第一次建立复制时必须经历的的阶段。触发全量复制的命令是 sync 和 psync。之前说过,这两个命令的分水岭版本是 2.8,redis 2.8 之前使用 sync 只能执行全量同步,2.8 之后同时支持全量同步和部分同步
。
流程如下:
介绍一下上图步骤:
- 发送 psync 命令(spync ? -1)
- 主节点根据命令返回 FULLRESYNC
- 从节点记录主节点 ID 和 offset
- 主节点 bgsave 并保存 RDB 到本地
- 主节点发送 RBD 文件到从节点
- 从节点收到 RDB 文件并加载到内存中
- 主节点在从节点接受数据的期间,将新数据保存到“
复制积压缓冲区
”,当从节点加载 RDB 完毕,再发送过去。(如果从节点花费时间过长,将导致缓冲区溢出,最后全量同步失败) - 从节点清空数据后加载 RDB 文件,如果 RDB 文件很大,这一步操作仍然耗时,如果此时客户端访问,将导致数据不一致,可以使用配置slave-server-stale-data 关闭.
- 从节点成功加载完 RBD 后,如果开启了 AOF,会立刻做 bgrewriteaof(AOF重写)。
以上加粗的部分是整个全量同步耗时的地方。
注意:
如过 RDB 文件大于 6GB,并且是千兆网卡,Redis 的默认超时机制(60 秒),会导致全量复制失败。可以通过调大 repl-timeout 参数来解决此问题。
Redis 虽然支持无盘复制(不使用磁盘作为中间存储),即直接通过网络发送给从节点,但功能不是很完善,生产环境慎用。
四.部分复制
当从节点正在复制主节点时,如果出现网络闪断和其他异常,从节点会让主节点补发丢失的命令数据,主节点只需要将复制缓冲区
的数据发送到从节点就能够保证数据的一致性,相比较全量复制,成本小很多。
前提时offset偏移量和本机的偏移量相差有没有超过复制积压缓冲区大小,否则还是全量复制
步骤如下:
- 当从节点出现网络中断,超过了 repl-timeout 时间,主节点就会中断复制连接。
- 主节点会将请求的数据写入到“复制积压缓冲区”,默认 1MB。
- 当从节点恢复,重新连接上主节点,从节点会将 offset 和主节点 id 发送到主节点
- 主节点校验后,如果偏移量数之后的数据在缓冲区中并且没有超过复制积压缓冲区大小,就发送 continue 响应 —— 表示可以进行部分复制
- 主节点将缓冲区的数据发送到从节点,保证主从复制进行正常状态。
五.增量复制
Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
六.心跳
主从节点在建立复制后,他们之间维护着长连接并彼此发送心跳命令。
心跳的关键机制如下:
主从都有心跳检测机制,各自模拟成对方的客户端进行通信,通过 client list 命令查看复制相关客户端信息,主节点的连接状态为 flags = M,从节点的连接状态是 flags = S。
主节点默认每隔 10 秒对从节点发送 ping 命令,可修改配置 repl-ping-slave-period 控制发送频率。
从节点在主线程每隔一秒发送 replconf ack 命令,给主节点上报自身当前的复制偏移量。
主节点收到 replconf 信息后,判断从节点超时时间,如果超过 repl-timeout 60 秒,则判断节点下线。
注意:为了降低主从延迟,一般把 redis 主从节点部署在相同的机房/同城机房,避免网络延迟带来的网络分区造成的心跳中断等情况。
七.异步复制
主节点不但负责数据读写,还负责把写命令同步给从节点,写命令的发送过程是异步完成,也就是说主节点处理完写命令后立即返回客户端,并不等待从节点复制完成。
异步复制的步骤很简单,如下:
- 主节点接受处理命令
- 主节点处理完后返回响应结果
- 对于修改命令,异步发送给从节点,从节点在主线程中执行复制的命令。
原理总结
可以看出,RDB 数据之间的同步非常耗时。所以,Redis 在 2.8 版本退出了类似增量复制的 psync 命令,当 Redis 主从直接发生了网络中断,不会进行全量复制,而是将数据放到缓冲区(默认 1MB)里,在通过主从之间各自维护复制 offset 来判断缓存区的数据是否溢出,如果没有溢出,只需要发送缓冲区数据即可,成本很小,反之,则要进行全量复制,因此,控制缓冲区大小非常的重要。
Redis哨兵机制
如果主节点挂了,从机是不能负责写命令的处理,必然不可取,就需要结合Redis哨兵。
Redis 主从复制的缺点:没有办法对 master 进行动态选举,需要使用 Sentinel 机制完成动态选举。
简介
- Sentinel (哨兵)进程是用于监控 Redis 集群中 Master 主服务器工作的状态(主服务器本身知道从服务器信息,相当于哨兵能够监控整个集群的信息)
- 在 Master 主服务器发生故障的时候,可以实现 Master 和 Slave 服务器的切换,保证系统的高可用( HA )
- 其已经被集成在 redis2.6+ 的版本中, Redis 的哨兵模式到了 2.8 版本之后就稳定了下来。
哨兵进程的作用
- 监控( Monitoring ): 哨兵( sentinel ) 会不断地检查你的 Master 和 Slave 是否运作正常。
- 提醒( Notification ): 当被监控的某个 Redis 节点出现问题时, 哨兵( sentinel ) 可以通过API 向管理员或者其他应用程序发送通知。
- 自动故障迁移( Automatic failover ):当一个 Master 不能正常工作时,哨兵( sentinel )会开始一次自动故障迁移操作
故障判定原理分析
- 每个 Sentinel (哨兵)进程以每秒钟一次的频率向整个集群中的 Master 主服务器, Slave 从服务器以及其他 Sentinel (哨兵)进程发送一个 PING 命令。
- 如果一个实例( instance )距离最后一次有效回复 PING 命令的时间超过
down-aftermilliseconds
选项所指定的值, 则这实例会被 Sentinel (哨兵)进程标记为主观下线( SDOWN )
。 - 如果一个 Master 主服务器被标记为
主观下线( SDOWN )
,则正在监视这个 Master 主服务器的所有 Sentinel (哨兵)进程要以每秒一次的频率确认 Master 主服务器的确进入了主观下线状态。 - 当有足够数量的 Sentinel (哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认 Master 主服务器进入了
主观下线状态( SDOWN )
, 则 Master 主服务器会被标记为客观下线( ODOWN )
。 - 在一般情况下, 每个 Sentinel (哨兵)进程会以每 10 秒一次的频率向集群中的所有Master 主服务器、 Slave 从服务器发送 INFO 命令。
- 当 Master 主服务器被 Sentinel (哨兵)进程标记为
客观下线( ODOWN )
时, Sentinel (哨兵)进程向下线的 Master 主服务器的所有 Slave 从服务器发送 INFO 命令的频率会从 10秒一次改为每秒一次。 - 若没有足够数量的 Sentinel (哨兵)进程同意 Master 主服务器下线, Master 主服务器的客观下线状态就会被移除。若 Master 主服务器重新向 Sentinel (哨兵)进程发送 PING 命令返回有效回复, Master 主服务器的主观下线状态就会被移除。
图解:
1.哨兵 Sentinel A/B/C 通过配置监控主节点Master
2.哨兵彼此之间也能互相通信(用来投票等)
3.哨兵通过主机的主从机制,可同时拥有了从机的信息
4.客户端正常访问直接访问主节点,感知不到哨兵的存在
5.如果master挂了,如果哨兵A先检测到(通过PING命令,超过down-aftermilliseconds指定的时间值还没有回应则认为失效),则哨兵A 就会对master标记为SDOWN,主观下线
6.同时监控该主服务器的所有哨兵会加快频率发送ping命令,每个都会确认是否是主观下线状态
7.如果有足够数量的哨兵进程(通过配置quorumc参数,默认是1,即一个哨兵就有生杀大权,也不能超过所有哨兵的数量,建议大于一半
)确认了Master进入了主观下线状态,就会将master标记为ODOWN:客观下线
8.如果确认客观下线后,就会进行自动故障迁移
自动故障迁移
- 它会将失效 Master 的其中一个 Slave(随机选) 升级为新的 Master , 并让失效 Master 的其他 Slave 改为复制新的 Master ;
- 当客户端试图连接失效的 Master 时,集群也会向客户端返回新 Master 的地址,使得集群可以使用现在的 Master 替换失效 Master 。(需要Redis Sentinel客户端,详细了解参考哨兵的客户端连接)
- Master 和 Slave 服务器切换后, Master 的 redis.conf 、 Slave 的 redis.conf 和sentinel.conf 的配置文件的内容都会发生相应的改变,即, Master 主服务器的 redis.conf配置文件中会多一行 slaveof 的配置, sentinel.conf 的监控目标会随之调换。
案例演示
哨兵本身也是一个redis节点
,也是一个完整的程序,也有自己端口等,只是命令不一样,可以和Redis节点放一块,也可以单独放到一个服务器
- 启动命令redis-sentinel
- 对应的配置文件叫sentinel.conf
核心配置
sentinel.conf :
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <master ip> <master port> <quorum>
sentinel monitor mymaster 192.168.10.133 6379 1
其他配置项说明
sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行同步,
# 这个数字越小,完成failover所需的时间就越长,
# 但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
# 可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给客户端:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
启动哨兵服务
通过 redis-sentinel 启动哨兵服务
./redis-sentinel sentinel.conf
Redis集群
redis3.0以后推出的redis cluster 集群方案,redis cluster集群保证了高可用、高性能、高可扩展性。
Redis的集群策略
- 推特:twemproxy
- 豌豆荚:codis
- 官方:redis cluster
Redis-cluster架构图
特点:
去中心化
:没有leader节点- 弊端,节点信息量大:每个节点都拥有全部集群的信息,每个节点都需要配置集群中其他节点的信息(不需要手动配置,自动的)
架构细节:
- (1)所有的redis节点彼此互联(
PING-PONG机制
),内部使用二进制协议优化传输速度和带宽. - (2)节点的fail是通过集群中超过半数的节点检测失效时才生效.
- (3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
- (4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value
数据分配规则:
crc16算法+哈希槽
Redis 集群中内置了 16384个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点
- 示例如下:
Redis-cluster投票:容错
(1)节点失效判断:集群中所有master参与投票,如果半数以上master节点与其中一个master节点通信超过(cluster-node-timeout)
,认为该master节点挂掉.
(2)集群失效判断:什么时候整个集群不可用(cluster_state:fail)?
- 如果集群任意master挂掉,且当前master没有slave,则集群进入fail状态。也可以理解成集群的[0-16383]slot映射不完全时进入fail状态。
- 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。
总结
这篇文章我们介绍了Redis主从复制的流程和工作原理,以及在复制过程中可能引发的问题。
虽然搭建一个复制集群很简单,但其中涉及到的细节也很多。Redis在复制过程也可能存在各种问题,我们需要设置合适的配置参数和合理运维Redis,才能保证Redis有稳定可用的副本数据,为我们的高可用提供基础。