Redis-主从复制流程详解

165 阅读11分钟

在Redis里面可以通过SLAVEOF命令或者设置slaveof选项,让一个服务器去复制另外一个服务器
命令示范: SLAVEOF IP:PORT

旧版复制功能

Redis的复制功能分为同步和命令传播两个操作

  • 同步操作主要是从服务器的数据库状态更新至主服务器当前所处的数据库状态
  • 命令传播操作则是在主服务器状态被修改的时候,主从服务器的状态不一致时,让主从服务器的数据库状态恢复到一致状态

同步流程

当从服务器接收到SLAVEOF命令的时候,需要对主服务器进行复制,这个时候从服务器对主服务器的同步操作是通过向主服务器发送SYNC命令来完成的

  • 从服务器向主服务器发送SYNC命令
  • 收到SYNC命令的主服务器执行BGSAVE命令,生成一个RDB文件,并使用一个缓冲区来记录从现在开始的执行的所有写命令
  • 当主服务器的BGSAVE命令执行完毕之后,会将生成的RDB文件发送给从服务器,从服务器接收这个RDB文件之后进行加载,将自己的数据库状态更新至主服务器执行BGSAVE命令的时候的状态
  • 最后,主服务器将记录在缓冲区的写命令发送给从服务器,然后从服务器执行这些命令,将自己的数据库状态更新至主服务器当前的状态

命令传播流程

当完成同步操作之后,主从服务器都会到达一致的状态,但是这个一致状态并不是一致不变的,当接下来主服务器接收客户端的写命令的时候,主服务器进行了修改,这就会导致从服务器状态不一致。
所以为了主从服务器回到一致状态,这个时候就需要传播操作:

  • 主服务器会将自己的接收到的会改变数据库状态的命令发送给从服务器
  • 从服务器然后再执行接收到的命令,这样主从服务器就会回到一致的状态

旧版复制功能的缺陷

在Redis中,从服务器对主服务的服务可以分为两种情况:

  • 一种是初次复制:从服务器第一次复制当前要复制的主服务器
  • 另外一种是断线后重复制:就是在命令传播阶段的主从服务器因为网络原因而中断了复制,然后从服务器通过自动重连接主服务器,并继续复制主服务器。 基于上面的情况,旧版复制功能对于第二种中断的情况,也是采用第一种的情况来让主服务器执行一次BGSAVE命令获得RDB文件,然后发送这个RDB文件给从服务器,但这种方式显然一种并不理想化的操作,因为这样的复制方式会大量复制从服务器已有的数据。

SYNC命令是一种非常消耗资源的操作

  • 主服务器需要执行BGSAVE命令,这个命令会占用主服务器大量的CPU,内存和磁盘I/O资源
  • 主服务器需要将自己的生成的RDB文件发送给从服务器,这个发送操作也会大量的网咯资源,并且会影响主服务器响应其他命令
  • 接收到RDB文件的从服务器需要载入这个文件,在这个载入的期间,从服务器会因为阻塞而没办法处理命令请求

新版复制功能

为了解决旧版复制功能的缺陷,从2.8版本开始,Redis使用PSYNC命令替代SYNC命令来执行复制时的同步操作
PSYNC命令有两种模式,一种是完全重同步,一种是部分重同步:

  • 完全重同步针对的就是初次复制的情况,与SYNC执行的命令一致
  • 部分重同步则是处理断线之后重连的情况,当从服务器重连之后,主服务器会发送断线期间主服务器接收到的写命令,这样从服务器只需要执行这些命令就可以将数据库的状态更新至主服务器当前所处的状态

部分重同步流程

部分重同步主要需要以下三个部分来实现:

  • 主服务器的复制偏移量和从服务器的复制偏移量
  • 主服务器的复制积压缓冲区
  • 服务器的运行ID

复制偏移量

对于复制的双方主从服务器都会维护一个复制偏移量

  • 主服务器每次向从服务器传播多少个字节,就会将自己的复制偏移量的值加上N
  • 从服务器每次收到主服务器传播来的N个字节的数据时,就会将自己的复制偏移量的值加上N 拥有了这个复制偏移量,这样主从服务器通过对比,就可以很快速的知道主从服务器是否处于一致的状态。

复制积压缓冲区

复制积压区就是主服务器维护的一个固定长度的先进先出的队列,默认大小为1MB
有了这个积压缓冲区,在传播期间,主服务器不仅会将写命令发送给所有的从服务器,还会将命令写入到复制积压缓冲区里面。因此积压缓冲区里面会保存一部分最近传播的写命令,并且复制积压缓冲区会为队列中的每个字节记录相对应的复制偏移量。
当从服务器重新连接主服务器时,从服务器通过PSYNC命令将自己的复制偏移量offset发送给主服务器,然后主服务器会根据这个复制偏移量决定对从服务器执行何种同步的操作:

  • 如果offset偏移量之后的数据,仍然存于复制积压缓冲区中,那么就会执行部分重同步操作
  • 如果offset偏移量之后的数据不存在复制积压缓冲区中,那么主服务器就会执行全重同步操作。

Redis的复制积压缓冲区设置的默认大小为1MB,如果主服务器需要执行大量的写命令,又或者从服务器断线之后重连所需要的时间比较长(时间越长,断开时间里面,主服务器执行的命令越多)那么这个大小也许就并不合适。所以说如果复制积压缓冲区的大小设置的如果并不好,那么PSYNC命令的复制同步模式就不能正常发挥作用。 复制积压缓冲区的最小最大可以根据 second * write_size_per_second 来进行估算

  • 其中second就是从服务器断线后重连上主服务器所需要的平均时间
  • write_size_per_second 则是主服务器平均每秒产生的写命令数据量 但通常为了安全起见,可以将复制积压缓冲区设置为 2 * second * write_size_per_second大小,这样可以保证大部分情况下,从服务器断线重连之后可以使用部分重同步来处理,至于如何来进行修改repl-backlog-size选项来说明

服务器运行ID

  • 每个Redis服务器都有自己的运行ID
  • 这个运行ID是服务器自动生成的,是一个40随机的十六进制字符组成 当主从服务器初次复制的时候,主服务器就将自己运行ID发送给从服务器,然后从服务器会保存下来。当从服务器断线并重新连接上一个服务器时,从服务器将向当前连接的主服务器发送之前保存的运行ID:
  • 如果主服务器接收到的运行ID与自己的运行ID相同,那么从服务器断线之前复制的就是当前自己这个服务器,那么会尝试采用部分重同步操作
  • 如果主服务器接收到的运行ID和自己的运行ID并不一致,那么从服务器断线之前复制的并不是自己这个服务器,那么就会执行完全重同步操作

PSYNC命令流程

PSYNC调用的方式有两种:

  • 如果从服务器以前没有复制过任何主服务器,或者之前执行过SLAVEOF no one命令,那么从服务器开始新的复制的时候,将会对主服务器发送PSYNC ? -1命令,主动请求主服务器进行完全的重同步
  • 相反地,如果从服务器已经复制过某个主服务器,那么从服务器在开始一次新的复制时将主服务发送PSYNC <runid> <offset>命令,其中runid是上一次复制的主服务器的运行ID,而offset则是当前从服务器的复制偏移量 当主服务器接收到从服务器发送的命令之后,会根据命令来判断执行哪种操作
  • 如果主服务器返回+FULLRESYNC <runid><offset>回复,这个表示主服务器将对从服务器进行完全重同步,runid就不多说了,offset就是主服务器当前的复制偏移量,从服务器接收到这个offset值,会将这个值作为自己的初始化偏移量
  • 如果主服务器发送来的是+CONTINUE回复,那么表示主服务器将与从服务器执行部分重同步操作,从服务器只要等着主服务器将自己缺少的数据发送过来就可以
  • 如果主服务器返回的是 -ERR回复,那么表示主服务器的版本低于2.8,它识别不了PSYNC这个命令

心跳检测

在主从服务器的传播阶段,从服务器默认会以每秒一次的频率向主服务器发送命令,主要是为了:

  • 检测主从服务器的网络连接状态
  • 辅助实现min-slaves选项
  • 检测命令丢失

检测主从服务器的网络连接状态

主从服务器可以通过发送和接收REPLCONF ACK命令来检查两者之间的网络连接状态是否正常:如果主服务器超过一秒没有收到从服务器发来的REPLCONF ACK命令,那么主服务器就知道从服务器之间的连接出现了问题,我们可以通过向主服务器发送INFO REPLICATION 命令,在列出的从服务器列表的lag一栏中,我们可以看到相应从服务器最后一次向主服务器发送REPLICONF ACK命令距离现在过了多少秒。

辅助实现min-slaves配置选项

Redis 的min-slaves-to-write 和 min-slaves-max-lag两个选项来防止主从服务器在不安全的情况下执行写命令。例如我们提供了下面的设置:

  • min-slaves-to-write 3
  • min-slaves-max-lag 10 这就表示如果从服务器的数量少于3个,或者3个从服务器的延迟(tag)值都大于或者等于10,那么主服务器将拒绝执行写命令,这里延迟值就是上面提到的INFO replication命令的lag值。

检测命令丢失

如果因为网络的故障,导致了主服务器向从服务器发送写的命令丢失了,那么当从服务器发送REPLCONF ACK命令时,主服务器发觉从服务器当前的复制偏移量少于自己的复制偏移量,那么主服务器就会根据从服务器的复制偏移量从复制积压缓冲区中找到从服务器需要的数据,然后将这些数据发送到从服务器中。
主服务器向从服务器补发缺失数据这一操作的原理与部分重同步操作的原理非常的相似,但这两个操作的区别是补发缺失数据的操作是在主从服务器没有断线的情况下执行,而部分重同步操作则是在主从服务器断线并重连之后执行的。