相关文章
Redis源码剖析——主从复制(2)
Redis源码剖析——主从复制(3)
Redis源码剖析——主从复制(4)
Redis源码剖析——主从复制(5)—共享复制缓冲区的方案
目录
前言
主要介绍主从复制的一些原理,相关代码分析可见同栏后续文章
一、同步原理
1.master节点存储维护
在主从复制模式下,Redis使用一对Replicaion ID, offset来唯一识别Master节点数据集的版本,要理解这个“版本“的概念需要认识Redis的以下三个概念:
- Replication ID(复制ID):每个Redis的主节点都用一个随机生成的字符串来表示在其成为master角色的那一刻其内部存储数据的状态,由源码可知在第一个从节点加入时,Redis初始化了复制ID。
- offset(复制偏移量):主从模式下,主节点会持续不断的向从节点传播引起数据集更改的命令,offset所表示的是主节点向从节点传递命令字节总数。它不是孤立存在的,需要配合复制积压缓冲区才能工作。
- backlog(复制积压缓冲区):它是一个环形缓冲区,用来存储主节点向从节点传递的命令,它的大小是固定的,可存储的命令有限,超出部分将会被删除。它即可用于部分同步,也可用于命令传播阶段的命令重推。
下图直观看下它们之间的关系。
图示说明:
(1)图示Redis角色为Master,其复制ID(replid)为xxxx,当前的复制偏移量(offset)为1010;
(2)它有一个复制积压缓冲区(backlog),容量(backlog_size)为100,backlog起点相对于offset的偏移量(backlog_off)为1000,当前backlog存储的命令字节数(backlog_hislen)为11个,对应了backlog中[1000,1010]偏移量范围内的字节;
(3)offset始终与backlog中最后一个字节的偏移量相同。
2.slave节点存储维护
- 复制ID:如果slave从未与任何master进行过主从复制,那就不存在;如果slave与master连接正常,复制ID存储在
server.master
内;如果slave与master连接断开,Redis会把它存储在server.cache_master
。 - 复制偏移量:与复制ID一致。
server.master
代表的是正在进行主从复制且正常工作的主节点信息;server.cache_master
是曾经正常进行主从复制工作的主节点信息,它是为部分同步做准备的。
3.部分同步与全量同步的使用情况
- 从节点与主节点建立连接后,需要从节点使用命令PSYNC <replid> <offset>向主节点发起同步请求,从节点会从server.cache_master中取出两个参数构建命令,然后发给主节点。注意:如果该从节点是全新的,从未与任何主节点进行主从复制,那么会使用特殊的命令:PSYNC ? -1。
- 主节点接收到命令,解析请求中的复制ID和offset,然后去判断当前请求是否可以使用部分同步。
- 能够使用部分同步需要满足以下两个条件(这里先不考虑主从切换导致的多复制ID情况):
- (1)复制ID与主节点的复制ID一致;
- (2)复制偏移量offset必须在backlog_off和offset的范围之间;
- 不能使用部分同步,就不得不使用全量同步了。
Redis-1:replid和offset为默认值,说明它从未与主节点进行过同步操作,所以是进行全量同步;
Redis-2:replid主从节点一致,slave_offset>=backlog_off并且slave_offset<offset,说明该从节点丢失的数据可以通过复制积压缓冲区找回,所以可以进行部分同步;
Redis-3:replid主从节点一致,slave_offset<backlog_off,说明该节点丢失的数据过多,通过复制积压缓冲区无法找回,所以是进行全量同步;
Redis-4:replid主从节点一致,之前不是与当前节点进行主从复制,所以是进行全量同步;
总结一下:部分同步其实是以全量同步为基础(得到复制ID),用复制积压缓冲区中的缓存命令做命令的增量同步逻辑,不过受制于复制积压缓冲区的容量,它可容忍的范围是有限的。这与持久化机制的AOF混合持久化如出一辙,也与mysql中主从复制的Binlog思路不谋而合。
4.命令传播
当完成同步操作之后,master-slave便会进入命令传播阶段,此时master-slave的数据是一致的。
当maste执行完新的写命令后,会通过传播程序把该命令追加至复制积压缓冲区,然后异步地发送给slave。slave接收命令并执行,同时更新slave维护的复制偏移量offset。命令传播如下图所示:
如果slave可以收到每条传播指令,并执行成功,便可以保持与master的数据一致状态。但是master并不等待slave节点的返回,master与slave是通过网络通信,由于网络抖动等因素,命令传播过程不保证slave真正接收到,那如何在传播阶段确保主从数据一致呢?
在命令传播阶段,每隔一秒,slave节点向master节点发送一次心跳信息,命令格式为REPLCONF ACK <offset>
。
命令中的offset是就是slave最新的复制偏移量,master接收后便会与自己的offset对比。如果从节点数据缺失,主节点会推送缺失的数据(这句话写的很虚,我在源码中没有找到相关逻辑,但是参考的所有文章都提到了这个点。如果哪位同学了解,烦请告知)。
二、Replication执行流程
主从复制的流程分为三个阶段:准备阶段、同步阶段、命令传播阶段,先上图再依次解释。
1.准备阶段
准备阶段完成与master的连接,主从之间通过命令一问一答处理状态检查及身份认证工作,为接下来的数据同步打好基础。
- 设置master节点host和port:这一步很好理解,当接收到slaveof命令后,Redis会设置master节点的host和port,同时设置服务的复制状态为
REPL_STATE_CONNECT
。 - 与master建立网络连接:与master的网络连接是在周期性函数
serverCron
中触发的,如果发现服务状态为REPL_STATE_CONNECT
,会调用函数connectWithMaster
创建与master的网络连接。连接成功后,修改复制状态为REPL_STATE_CONNECTING
,同时触发回调函数syncWithMaster
开始与master节点的命令交互。连接失败,则等待下次重试。 - 发送PING命令:与master连接创建成功后,slave向master发送PING命令。通过PING命令检查网络连接的读写状态是否正常,还可以检查master能否正常处理命令请求。如果slave读取PING命令回复超时或者master返回错误信息,将断开并重连服务器。服务器返回PONG,则继续执行下一个步骤。
- 身份认证:检查从服务器是否设置masterauth,设置则进行身份验证,未设置则跳过该步骤。身份认证失败,则断开重连。
- 发送slave端口信息:slave向主服务器发送自己的监听端口号,master收到之后会将端口号记录到slave对应的状态属性中,可通过命令
client list
查看。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
2.同步阶段
2.1 确定同步方式
该阶段首先要做的就是master-slave共同协作确定同步的方式,简单来说就是两步:slave发送PSYNC命令、master判断决定同步方式进行回复。
slave发送PSYNC指令。 PSYNC命令要为参数replid和offset赋值,这里需要区分两种情况:
- 如果slave是第一次与当前master进行主从复制,则使用默认值:
replid=?; offset=-1
; - 如果slave之前与该master进行过全量复制,则根据缓存的master信息、复制偏移量进行赋值;
怎么判断slave是第一次进行主从复制呢?
Redis核心结构server的cached_master
保存了master节点的信息,只有进行过主从复制才会赋值,否则为空。
master判断决定同步方式。 master接收命令后由syncCommand
函数处理,然后调用masterTryPartialResynchronization
函数判断同步的方式,从代码分析可知,有以下几种情况:
- offset解析失败,执行全量同步;
- replid与master的复制ID不一致,执行全量同步;
- offset不在master的backlog缓冲区范围内,执行全量同步;
- 其余满足部分同步条件,执行部分同步;
接下来,从master和slave两个角度详细看下全量同步和部分同步的执行流程。
2.2 全量同步流程
先看master。 确认需要执行全量同步后,master直接进入处理流程。这里提一下,全量同步过程中Redis是依靠slave的状态来驱动整个流程的,我先通过一张图描述下全量同步过程及slave的状态流转,再做说明:
- 当主节点收到从节点发来”PSYNC”命令,并且需要完全重同步时,将从节点的状态置为REDIS_REPL_WAIT_BGSAVE_START,表示该从节点等待主节点后台RDB数据转储的开始;
- 接下来,当主节点开始在后台进行RDB数据转储时,将从节点的状态置为REDIS_REPL_WAIT_BGSAVE_END,表示该从节点等待主节点后台RDB数据转储的完成;
- 如果主节点在进行后台RDB数据转储时,使用的是有硬盘复制的方式,则RDB数据转储完成时,将从节点的状态置为REDIS_REPL_SEND_BULK,表示接下来要将本地的RDB文件发送给客户端了;当所有的RDB数据发送完成后,将从节点的状态置为REDIS_REPL_ONLINE,表示可以向从节点发送累积的命令流了。
在看slave侧。 在准备阶段,slave发送psync指令后,就等待master的回复,当收到全量同步的回复后,开始执行全量同步流程。过程如下:
- 如果slave还有级联的slave,则断开所有与它们的网络链接,并清空复制积压缓冲区;
- 创建rdb临时文件,接收master传输的文件流并写入;
- 停止正在进行的rdb持久化、aof持久化流程;
- 重命名临时文件为正式的rdb文件,执行数据加载;
- 基于当前与master的网络链接,创建slave的客户端,把master作为slave的客户端;
- 设置slave的复制id,创建复制积压缓冲区;
- 进入命令传播阶段;
2.3 部分同步流程
对于全量同步,部分同步要简单的多。
master判定可以使用部分同步方式,执行以下流程:
- 修改slave状态为
SLAVE_STATE_ONLINE
,并把slave加入从机列表; - 向slave回复部分同步命令,"+CONTINUE replid";
- 按照slave请求的offset,从复制积压缓冲区提取命令发送至slave;
slave接收到部分同步的回复后,执行以下流程:
- 对比master复制ID是否发生改变,如果改变了,则更新复制ID,并把原来的复制ID转移至复制ID2;如果有级联的slave,需要断开连接,让他们重连;
- 基于当前与master的连接,创建slave的客户端,准备接收命令。
- 接收master传输的命令并执行;
- 进入命令传播阶段。
三、Replication相关其他问题
1.从机只读
默认情况下,从机工作在只读模式下,即无法对从机执行写指令。如下图,对从机执行写指令将会返回错误。
如果slave也配置了自己的从服务器(sub-slave),那么sub-slave只会同步从master服务器同步到slave的数据,而不会同步我们直接写入slave服务器的数据。如:A--->B--->C
,如果B关闭了只读模式,C只会同步来自A的命令且与A保持一致。
2.过期key处理
Redis可以通过设置key的过期时间来限制key的生存时间,Redis处理key过期有惰性删除和定期删除两种机制,这一机制依赖Redis实例的计时能力。如果主机、从机同时启用key过期的处理机制,可能会导致一些问题。为此,Redis采取了三个技术手段来解决key过期的问题:
- 从机禁用主动key过期机制。主机在执行key过期后,会以
DEL
指令的方式向所有从机传播指令,从而保证从机移除过期的key。 - 依赖主机的key过期机制是无法做到实时性的,所以针对读操作,从机将会按照自己的时钟向客户端返回key不存在。
- 为防止Lua脚本执行期间key过期,Lua脚本将会传播给从机执行。
3.min-replica机制
Redis主从复制不仅仅是解决主机、从机之间数据同步的问题,它还需要保证数据的安全性。这里的安全性主要是指主从之间数据同步达到一致的效率,以及主从结构下读写分离场景中分布式系统的可靠性。
Redis采用异步复制机制,它无法真正保证每个从机都能准确的收到传播的指令,所以主从之间必然会存在命令丢失的时间窗口。
为此,Redis引入了min-replicas
选项,该机制在redis.conf中有两个配置项:
- min-replicas-to-write <number of replicas>:至少有N个从机才能写入数据。保证从机最低数量。
- min-replicas-max-lag <number of seconds>:如果每个从机的延迟值大于N,则拒绝写入数据。保证主从同步延迟。
这一机制是通过从机与主机之间心跳来实现的,如上文所讲,从机每隔一秒向主机发送一次心跳数据,基于心跳,主节点可以:
- 更新从机同步确认时间:基于主节点时间及同步确认时间计算延迟值。
- 更新主从最后通信时间:用于从机通信超时检测,如果通信超时,主机会移除从机。
4.主机关闭持久化时复制的安全性
当master关闭了持久化时,如果发生故障后自动重启时,由本地没有保存持久化的数据,重启的Redis内存数据为空,而slave会自动同步master的数据,就会导致slave的数据也会被清空。
所以,我们应该尽可能为master节点开启持久化,这样可以防止Redis故障重启时数据丢失,进而导致slave数据被清除。如果确实无法开启持久化机制,那应该配置master节点无法自动重启,确保从机可以成为新的master节点,防止数据被清除。
5.master节点发送ping指令频度低,网络存在丢包
master节点默认10s向slave节点发送一次ping指令,因为master节点不仅要处理大量的写任务,还可能维护着多个master,所以ping设置的不太及时。但是当ping指令在网络中存在丢包时,master节点如果设置的超时时间太短,就会导致master节点与slave节点断开连接。
解决方案有:提高master节点ping的频度,超时时间repl-time设置为ping指令时间的5~10倍。
6.网络信息不同步,数据发送有延迟
当主从同步中网络数据发送有延迟的时候,就会造成多个slave获取到的数据不同步,解决方案是优化master节点和slave节点的网络环境,通常是放置在一个机房部署。另外要监控master和slave节点的延迟,如果延迟过大,可以暂时屏蔽对slave节点的访问。