Redis主从同步原理


Redis主从同步

Redis Replication是一种简单、易用的主从模式(master-slave)的复制机制,它能够使得slave节点成为与master节点完全相同的副本。每次与master节点连接中断后slave节点会自动重联,并且无论master节点发生什么,slave节点总是尝试达到与master节点一致的状态。Redis采取了一系列的辅助措施来保证数据安全。

Redis2.8版本之前,每次slave节点断线重联后,只能进行全量同步。在2.8版本之后进行了重新设计,引入了部分同步的概念。

主从复制机制包含三个部分:全量同步、部分同步、命令传播。

主从同步的几个重要参数

  • Replication ID(复制ID或masterId):每个Redis的主节点都用一个随机生成的字符串来表示在某一时刻其内部存储数据的状态,“某一时刻”可以理解为其成为master角色的那一刻,由源码可知在第一个从节点加入时,Redis初始化了复制ID。
  • offset(复制偏移量):主从模式下,主节点会持续不断的向从节点传播引起数据集更改的命令,offset所表示的是主节点向从节点传递命令字节总数。它不是孤立存在的,需要配合复制积压缓冲区才能工作。
  • backlog(复制积压缓冲区):它是一个环形缓冲区,用来存储主节点向从节点传递的命令,它的大小是固定的,可存储的命令有限,超出部分将会被删除。它即可用于部分同步,也可用于命令传播阶段的命令重推。

全量同步

master节点创建全量数据的RDB快照文件,通过网络连接发送给slave节点,slave节点加载快照文件恢复数据,然后再继续发送复制过程中复制积压缓冲区内新增的命令,使之达到数据一致状态。

  • 从节点与主节点建立连接后,需要从节点使用命令PSYNC <replid> <offset>向主节点发起同步请求,从节点会从server.cache_master中取出两个参数构建命令,然后发给主节点。注意:如果该从节点是全新的,从未与任何主节点进行主从复制,那么会使用特殊的命令:PSYNC ? -1
  • 主节点接收到命令,解析请求中的复制ID和offset,然后去判断当前请求是否可以使用部分同步。
  • 能够使用部分同步需要满足以下两个条件(这里先不考虑主从切换导致的多复制ID情况):
    • 复制ID与主节点的复制ID一致;
    • 复制偏移量offset必须在backlog_off和offset的范围之间;
  • 不能使用部分同步,就不得不使用全量同步了。

部分同步

部分同步其实是以全量同步为基础(得到复制ID),用复制积压缓冲区中的缓存命令做命令重放的增量同步逻辑,不过受制于复制积压缓冲区的容量,它可容忍的范围是有限的。这与持久化机制的AOF混合持久化如出一辙,也与mysql中主从复制的Binlog思路不谋而合。

命令传播

如果master-slave节点保持连接,master节点将持续向slave节点发送命令流,以保证master节点数据集发生的改变同样作用在slave节点数据集上,这些命令包含:客户端写请求、key过期、数据淘汰以及其他所有引起数据集变更的操作。

当完成同步操作之后,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对比。如果从节点数据缺失,主节点会推送缺失的数据

确定同步方式

该阶段首先要做的就是master-slave共同协作确定同步的方式,简单来说就是两步:slave发送PSYNC命令、master判断决定同步方式进行回复。

slave发送PSYNC指令。 PSYNC命令要为参数replid和offset赋值,这里需要区分两种情况:

  • 如果slave是第一次与当前master进行主从复制,则使用默认值:replid=?; offset=-1
  • 如果slave之前与该master进行过全量复制,则根据缓存的master信息、复制偏移量进行赋值;

怎么判断slave是第一次进行主从复制呢?
Redis核心结构server的cached_master保存了master节点的信息,只有进行过主从复制才会赋值,否则为空。

  • offset解析失败,执行全量同步;
  • replid与master的复制ID不一致,执行全量同步;
  • offset不在master的backlog缓冲区范围内,执行全量同步;
  • 其余满足部分同步条件,执行部分同步;

全量同步流程

master侧
  • 修改当前slave的复制状态为SLAVE_STATE_WAIT_BGSAVE_START(等待bgsave操作开始)并把它加入从机列表;
  • 如果当前slave是第一个从机,需要生成新的Replication ID、清除Replication ID2、初始化复制积压缓冲区;
  • 开始执行bgsave,slave对应的bgsave流程开始后,其复制状态会修改为SLAVE_STATE_WAIT_BGSAVE_END,需要考虑以下三种情况:
    • 如果已经有子进程在执行,并且为磁盘模式:复用当前的bgsave,保存缓冲区状态;然后向slave回复全量同步,offset为正在进行bgsave启动时的复制偏移量;
    • 如果已经有子进程在进行,但是为无盘模式:暂时不执行bgsave,等待周期性检查时触发;
    • 如果没有子进程在进行:开启bgsave;然后向slave回复全量同步,offset为当前master的复制偏移量。
  • master执行bgsave后就去处理别的请求了,bgsave执行成功后会在serverCron中触发backgroundSaveDoneHandler,进而调用updateSlavesWaitingBgsave,它做的事情就是依次向所有复制状态为SLAVE_STATE_WAIT_BGSAVE_END的slave传输rdb文件。rdb传输之前修改slave复制状态为SLAVE_STATE_SEND_BULK,传输完成后修改状态为SLAVE_STATE_ONLINE
slave侧

在准备阶段,slave发送psync指令后,就等待master的回复,当收到全量同步的回复后,开始执行全量同步流程。过程如下:

  • 如果slave还有级联的slave,则断开所有与它们的网络链接,并清空复制积压缓冲区;
  • 创建rdb临时文件,接收master传输的文件流并写入;
  • 停止正在进行的rdb持久化、aof持久化流程;
  • 重命名临时文件为正式的rdb文件,执行数据加载;
  • 基于当前与master的网络链接,创建slave的客户端,把master作为slave的客户端;
  • 设置slave的复制id,创建复制积压缓冲区;
  • 进入命令传播阶段;
部分同步流程

相对于全量同步,部分同步要简单的多。

master判定可以使用部分同步方式,执行以下流程:

  • 修改slave状态为SLAVE_STATE_ONLINE,并把slave加入从机列表;
  • 向slave回复部分同步命令,”+CONTINUE replid”;
  • 按照slave请求的offset,从复制积压缓冲区提取命令发送至slave;

slave接收到部分同步的回复后,执行以下流程:

  • 对比master复制ID是否发生改变,如果改变了,则更新复制ID,并把原来的复制ID转移至复制ID2;如果有级联的slave,需要断开连接,让他们重连;
  • 基于当前与master的连接,创建slave的客户端,准备接收命令。
  • 接收master传输的命令并执行;
  • 进入命令传播阶段。

其他细节

从机只读

默认情况下,从机工作在只读模式下,即无法对从机执行写指令。如下图,对从机执行写指令将会返回错误。

若要该更此模式,可在配置文件修改如下选项:

# 默认是yes-只读,no-可写
slave-read-only yes/no 

如果slave也配置了自己的从服务器(sub-slave),那么sub-slave只会同步从master服务器同步到slave的数据,而不会同步我们直接写入slave服务器的数据。如:A--->B--->C,如果B关闭了只读模式,C只会同步来自A的命令且与A保持一致。

过期key处理

Redis可以通过设置key的过期时间来限制key的生存时间,Redis处理key过期有惰性删除和定期删除两种机制,这一机制依赖Redis实例的计时能力。如果主机、从机同时启用key过期的处理机制,可能会导致一些问题。为此,Redis采取了三个技术手段来解决key过期的问题:

  • 从机禁用主动key过期机制。主机在执行key过期后,会以DEL指令的方式向所有从机传播指令,从而保证从机移除过期的key。
  • 依赖主机的key过期机制是无法做到实时性的,所以针对读操作,从机将会按照自己的时钟向客户端返回key不存在。
  • 为防止Lua脚本执行期间key过期,Lua脚本将会传播给从机执行。

min-replica机制

Redis主从复制不仅仅是解决主机、从机之间数据同步的问题,它还需要保证数据的安全性。这里的安全性主要是指主从之间数据同步达到一致的效率,以及主从结构下读写分离场景中分布式系统的可靠性。

Redis采用异步复制机制,它无法真正保证每个从机都能准确的收到传播的指令,所以主从之间必然会存在命令丢失的时间窗口。

为此,Redis引入了min-replicas选项,该机制在redis.conf中有两个配置项:

  • min-replicas-to-write :至少有N个从机才能写入数据。保证从机最低数量。
  • min-replicas-max-lag :如果每个从机的延迟值大于N,则拒绝写入数据。保证主从同步延迟。

通过info replication可以查看从机数量(connected_slaves)、每个从机的延迟值(lat)。

这一机制是通过从机与主机之间心跳来实现的,如上文所讲,从机每隔一秒向主机发送一次心跳数据,基于心跳,主节点可以:

  • 更新从机同步确认时间:基于主节点时间及同步确认时间计算延迟值。
  • 更新主从最后通信时间:用于从机通信超时检测,如果通信超时,主机会移除从机。

文章作者: SongAn
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 SongAn !
  目录