Redis主从复制(2.8版本及以后)
一、主从复制概述
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。给出数据的节点称之为主节点(master),获取数据的节点称之为从节点(slave)。数据的复制是单向的,只能由主节点到从节点。
默认情况下,每台机器都是主节点,可以通过slaveof命令设置从节点,在要作为从节点的redis客户端输入slaveof <master-host> <master-port>或在要作为从节点的redis配置文件中配置slaveof <master-host> <master-port>,如果master有密码,还需配置密码认证masterauth。
在master-slave模式下,一个master可以有多个slave,但一个slave只能有一个master。如下图所示:
主从复制作用:
数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
高可用:主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
二、复制功能的实现
redis从2.8版本开始使用PSYNC命令代替SYNC命令来执行复制时的同步操作。PSYNC命令可分为全量同步(full resynchronization)和增量同步(partial resynchronization)
全量同步:用于初次复制的情况,slave发送PSYNC命令到master,master创建并发送RDB文件及缓冲区里的写命令给slave。
增量同步:用于网络中断等情况后的复制,只将中断期间master执行的写命令发送给从节点,与全量同步相比更加高效。需要注意的是,如果网络中断时间过长,导致master没有能够完整地保存中断期间执行的写命令,则无法进行增量同步,仍需要全量同步。
1、全量同步过程
(1)、slave发送PSYNC命令到master,master判断需要全量同步还是增量同步(判断方式在“PSYNC命令”标题下),如果需要全量同步
(2)、master执行bgsave,在后台生成RDB文件,并使用复制缓冲区记录从现在开始执行的所有写命令。将RDB文件发送给slave
(3)、slave收到RDB文件后先清理自己的数据,然后载入RDB文件的数据,将自己的状态更新到master bgsave时的状态
(4)、master将刚才复制缓冲区的写命令顺序的发送给slave,slave执行这些写命令将自己的状态更新到和master保持一致。

2、增量同步
增量同步由一下三个部分构成:
- master和slave的复制偏移量。
- master的复制缓冲区。
- 服务器的运行ID(runID)。
增量同步的实现在下一个标题(增量同步的实现)中说明。
三、增量同步的实现
1、复制偏移量
执行复制的双方分别会维护一个复制偏移量
- master每次向slave传播N个字节的数据时,就将自己的复制偏移量的值加上N。
- slave每次收到master传来的N个字节的数据时,就将自己的复制偏移量的值加上N。

如图所示master和slave的复制偏移量都是10086.如果这是master向3个slave传播33个字节的数据,那么master的复制偏移量将会更新为10086+33=10119,而3个slave收到master传播的数据后,也会将复制偏移量更新为10119。正常情况下master和slave的复制偏移量应该是保持一致的。
- 如果master slave处于一致,那么两者的复制偏移量总是相同的。
- 相反,如果master slave的复制偏移量不相同,则说明此事主从不一致。
考虑以下这个例子:master slave的复制偏移量都为10086,但是就在master向slave传播33个字节长度数据之前,slave-1断线了,那么master传播的数据将只有slave-2、slave-3收到并将自身的复制偏移量offset更新为10119,而断线的slave-1的复制偏移量offset依旧是10086,说明slave-1与master不一致,如下图:

假设slave-1断线后就立即重连master并且连接成功,那么接下来slave-1向master发送PSYNC命令,报告slave-1当前的offset=10086,这时master会对slave-1进行全量同步还是增量同步呢?答案在复制缓冲区(也叫复制积压缓冲区)中。
2、复制缓冲区
复制缓冲区是由master维护的一个固定长度的队列,默认大小为1MB。
固定长度的队列:长度是固定的,当入队元素的数量大于队列长度时,队列头部的元素会被弹出,将新的元素加入到队列尾部。

当master进行命令传播时,不仅会将写命令发送给slave,还会将写命令写入复制缓冲区。因此master的复制缓冲区里面还会保存着最近传播的写命令,并且复制缓冲区会为队列中的每个字节记录相应的偏移量,如表格所示:
| 偏移量 | ... | 10087 | 10088 | 10089 | 10090 | 10091 | 10092 | 10093 | ... |
| 字节值 | ... | '$' | 3 | '\r' | '\n' | 'S' | 'E' | 'T' | ... |
当slave-1连上master时,slave-1会通过发送PSYNC命令将自己的offset发送给master,master会根据slave-1的offset来决定执行全量同步还是增量同步。
- master会判断如果slave-1的offset之后的数据(也就是slave-1的offset+1开始的数据)仍然存在于master的复制缓冲区,则进行增量同步。
- 如果slave-1的offset之后的数据已经不再复制缓冲区了,则进行全量同步。
3、服务运行ID(runid)
master和slave都会有自己的运行ID。运行ID在服务启动时自动生成,有40个随机的十六进制字符组成。
当slave初次复制master时,master会将自己的运行ID发送给salve保存。当slave断线重连后请求数据同步时会将这个运行ID发送给master
- 如果slave保存的runid和当前连接的master的runid相同,那么说明slave失联之前连接的master就是当前连接的master,master根据slave的offset判断全量还是增量同步。
- 如果runid不相同,说明salve失联后重新连接的master被更换,则新的master对salve做全量同步。
四、PSYNC命令
PSYNC命令的调用方式有两种:
- 如果slave以前没有复制过任何master,或者slave之前执行过SLAVEOF no one命令,那么slave在开始一次新的复制时将向master发送PSYNC ? -1命令,主动请求master进行全量同步。
- 如果slave已经复制过某个master,那么slave在开始一次新的复制时将向master发送PSYNC <runid> <offset>命令。其中runid是上一次复制时的master的运行id,offset是slave的复制偏移量。master在接收到该命令时自行判断采用何种同步方式。
master接收到PSYNC命令时,会向slave回复下面情况的其中一种:
- 如果master返回+FULLRESYNC <runid> <offset>,那么表示master与slave进行全量同步作。runid表示master的运行id,slave将会保存这个id,在下次发送PSYNC命令时使用;offset表示当前master的复制偏移量,slave将这个值作为自己的初始偏移量。
- 如果master返回+CONTINUE,那么表示master将与slave进行增量同步。master只需将offset之后的数据同步给slave即可,同时两边维护offset。
- 如果master返回-ERR,那么表示master的版本低于2.8,不识别PSYNC命令,slave将向master发送SYNC命令,并与master进行全量同步。

五、复制的步骤
通过向slave发送SLAVEOF命令可以让slave去复制master。
1、设置master的ip+port
可以提前在欲作为slave的redis配置文件中配置slaveof <master-host> <master-port>,也可由客户端向slave节点发送slaveof master-host master-port。如:slaveof 127.0.0.1 6379。
SLAVEOF命令是一个异步命令,在完成master-host属性和master-port属性的设置之后,slave向客户端返回OK,表示复制命令已经被接收,真实的复制工作将在返回OK后进行。
2、建立连接
建立连接方便slave向master发送同步请求,master向slave发送自身信息及数据
3、发送ping命令
发送ping命令检验连接是否可用
4、身份认证
如果master设置了访问密码,则需要在slave配置文件中配置masterauth。在需要身份认证的情况下slave向master发送auth <password>进行认证。
5、发送端口信息
slave发送REPLCONF listening-port <slave-port>命令向master上报监听端口。如slave端口为12345

master收到这个命令后将slave的端口记录在对应的slave_listening_port属性中
6、同步
slave发送PSYNC命令请求数据同步
7、命令传播
当完成同步后,主从服务器进入命令传播阶段,此时master只要一直将自己执行的写命令发送给slave,而slave接收并执行master发来的写命令就可以保证主从状态一致了
六、心跳检测
在命令传播阶段,slave默认会以每秒一次的频率向master发送REPLCONF ACK <replication_offset>命令。replication_offset表示slave当前的复制偏移量。
发送REPLCONF ACK命令对主从服务器有3个作用:
- 检测主从服务器的网络连接状态。
- 辅助实现min-slaves选项。
- 检测命令丢失。
1、检测主从服务器的网络连接状态
如果master超过一秒没有收到slave发来的REPLCONF ACK命令,那么master认为主从之间连接出现了问题,slave失联了。
通过在master上执行INFO replcation命令,在列出的从服务器信息列表的lag一栏中可以看到相应的slave最后一次发送REPLCONF ACK命令距离现在过了多少秒。在一般情况下lag的值应该是0秒或者1秒,如果lag值超过1秒,则说明master和对应的slave之间的链接出现了问题,对应slave失联了。
2、辅助实现min-slaves选项
Redis的min-slaves-to-write和min-slaves-max-lag两个选项可以防止master在不安全的情况下执行写命令。例如master的配置文件有以下设置:
min-slaves-to-write 3
min-slaves-max-lag 10
那么在slave的数量少于3个(min-slaves-to-write的配置值)或者所有slave的延迟(lag值)都大于等于10秒(min-slaves-max-lag的配置值)时,master将拒绝执行写命令。
3、检测命令丢失
如果因为网络故障,master传播给slave的写命令在半路丢失,那么当slave向master发送REPLCONF ACK命令,master会发现slave的offset小于自己的偏移量(即slave-offset<master-offset),此时master会根据slave提交的offset判断该offset之后的数据是否都在复制积压缓冲区中。都在的话,则根据该offset进行增量同步;否则进行全量同步。

七、主从复制满足的CAP理论
分布式系统的节点往往都是分布在不同的机器上进行网络隔离开的,这意味着各个节点之间必然会存在网络断开的风险,这个网络断开的场景我们称之为网络分区。
在网络分区发生时,两个分布式节点之间无法进行通信,我们对一个节点进行的修改操作将无法同步到另外一个节点,所以数据的一致性将无法满足,因为两个分布式节点的数据不再保持一致。除非我们牺牲可用性,也就是暂停分布式节点服务,在网络分区发生时,不再提供修改数据的功能,直到网络状况完全恢复正常再继续对外提供服务。一句话在发生网络分区时,一致性和可用性不能都兼顾。
- Redis主从复制是异步进行的,数据写入master后向slave同步需要一定的时间,在这段时间内主从会出现数据不一致的情况,即使复制过程中slave失联,在失联恢复后slave会采用多种策略努力追赶上落后的数据,继续尽力保持和master一致。虽然不能满足实时一致性,但可以保证最终一致性。
- 当客户端在 Redis 的master节点修改了数据后,立即返回,即使某个slave节点失联,master节点和其余slave节点依旧可以正常对外提供修改服务,所以 Redis 满足可用性。
master和slave之间数据不一致的程度有很多影响因素,如:主从节点之间的网络状况、主节点写命令的执行频率、主节点配置文件中的repl-disable-tcp-nodelay配置等有关。
repl-disable-tcp-nodelay配置有两种值:
- yes:master会合并小的TCP包从而节省带宽,但会增加同步延迟,造成master与slave数据不一致。该配置值注重性能
- no:master会立即发送同步数据,没有延迟。该配置值注重一致性
八、主从复制中的问题
主从复制可以实现读写分离,在写少读多的场景下可以通过扩展slave节点的个数来减轻各个slave节点的访问压力,提高Redis的读并发支持。
1、同步延迟问题
同步延迟会导致主从在某个时间片段内状态不一致。因为命令传播阶段是异步进行,所以在命令传播阶段短暂的时间内主从状态一定是不一致的,在网络状态通常、主从连接没有出现故障的情况下,最终的状态是保持一致的。

2、slave读到过期数据
在主从复制模式下,slave不会主动删除数据,都是收到master的删除命令才执行删除操作,slave自身不会对过期key做删除操作。
Redis删除过期key的两种策略:
- 惰性删除:客户端访问某个key时,服务器判断这个key是否已过期,如果过期则删除,然后执行客户端的命令。
- 定期删除:服务器通过定时任务删除过期数据
master不管采用这两种哪种方式进行过期删除,slave节点中都有可能会存在已经过期的数据,slave-client依旧可以访问到这部分数据,直到收到master的删除命令,这些数据才会在slave节点被删除。
Redis3.2版本及以后,slave-client读取数据时,slave节点会判断key是否已过期,对于过期key返回null给slave-client。
3、故障切换
在没有使用哨兵的读写分离场景下,应用针对读和写分别连接不同的Redis节点;当master出现故障时会失去写能力。如果加上哨兵的话,可以在master出现故障时,在众多slave节点中选举出一个新的master,保证集群的高可用性,依旧可以对外提供服务。

浙公网安备 33010602011771号