这里就Redis Replication复制功能的基本原理进行介绍
旧版复制的基本原理
在Redis 2.8版本之前,其复制功能大体可以分为两种操作:「同步、传播命令」。前者用于将Slave的数据库状态更新到Master的数据库状态。具体地,当客户端向Slave发送SLAVEOF命令,要求Slave复制Master时,Slave即会执行同步操作。基本流程如下所示
-
Slave向Master发送SYNC命令 -
Master在收到SYNC命令后开始执行BGSAVE命令,在后台创建RDB文件。同时会使用一个缓冲区,用于记录、保存在创建RDB文件期间所有的写命令 -
当Master的BGSAVE命令执行完毕后,Master会将RDB文件发送给Slave。Slave接收、载入该RDB文件 -
Master将缓冲区中的所有写命令发送给Slave,Slave接收、执行所有写命令 -
至此,Slave的数据库状态就更新到了Master的数据库状态。即Slave完成了同步
而后者则用于在主从同步建立后,Master每次收到、执行完客户端新的写命令后。会将该写命令传播、发送至Slave。以保证主从的数据一致性
新版复制的基本原理
但旧版复制的实现存在一个严重的缺陷。当主从之间一旦由于网络中断而断开连接、停止复制,而一定时间后Slave通过自动重连恢复了与Master的网络通讯。由于此时Slave已经滞后于Master了,故其会向Master再次发送SYNC命令以重新开始从头同步。显然即使主从断开的时间不长,二者之间可能也只是滞后了几条数据。但Master依然还是需要对数据库的全部数据创建RDB文件,然后发送给Slave。此举的效率大大降低
故从Redis 2.8版本开始,其引入了PSYNC命令用于替代此前的SYNC命令,实现对「同步」操作进行优化。PSYNC命令具有完整重同步、部分重同步两种模式。对于前者而言其基本原理与SYNC命令类似,通过Master向Slave发送其创建的RDB文件、缓冲区中的写命令来实现;而对于后者而言,其适用于Slave断线重连的场景。Master只需将断线期间的写命令再次发送给Slave即可,而无需去执行非常消耗资源的RDB文件创建工作
显然一旦Master、Slave同步完成后,后续Master在每次收到、执行完客户端新的写命令后,只需将该写命令传播、发送至Slave即可。以时刻保证二者之间的数据一致性
PSYNC命令的基本原理
Replication Offset 复制偏移量
在PSYNC命令中引入了「Replication Offset复制偏移量」的概念,包括Master的复制偏移量、Slave的复制偏移量。每次当Master向Slave传播N个字节的数据命令时,Master就会将自身的复制偏移量加上N;每次当Slave从Master接收到N个字节的数据后,Slave就会将自身的复制偏移量加上N。这样只需对比Master、Slave的复制偏移量,即可知道二者之间是否处于一致的状态。具体地来说,当二者之间的复制偏移量完全一样,则说明二者的数据状态是一致的;反之则说明二者的数据状态不一致
例如,Master的复制偏移量为255,Slave A的复制偏移量为255,Slave B的复制偏移量为200。此时说明:Master、Slave A之间的数据状态是一致的;Master、Slave B之间的数据状态则不一致。那么要让Slave B与Master保持一致,Master只需再次向Slave B补传后者所缺失的55(255-200)个字节的数据即可
Replication Backlog 复制积压缓冲区
为了能够实现Master对Slave B补传数据,就需要在Master中引入一个「Replication Backlog复制积压缓冲区」的概念。其是Master中的一个固定长度的FIFO队列。每次Master向所有Slave传播、发送命令的同时,也会将该命令写入复制积压缓冲区当中。同时也会在复制积压缓冲区记录其中每个字节数据所对应的偏移量。
这样当Slave断线重连后,Slave会通过PSYNC命令向Master告知Slave自身的复制偏移量。Master即可根据Slave的复制偏移量offset确定是采用部分重同步、还是完整重同步。具体地
-
如果offset偏移量之后的数据(即从偏移量offset+1开始的数据)依然存在于Master的复制积压缓冲区当中,则Master就可以进行部分重同步,只需将保存在复制积压缓冲区中offset偏移量之后的所有数据再次发送给Slave即可 -
如果offset偏移量之后的数据(即从偏移量offset+1开始的数据)不存在于Master的复制积压缓冲区当中,则Master就只能Slave进行完整重同步,以让二者达到一致。因为复制积压缓冲区是固定长度的,其只能保存Master在最近一段时间内的所有数据、命令。并不能保存无限期限的数据、命令
Redis服务端的运行ID
事实上对于Redis服务端而言,无论是Master还是Slave。其在启动运行后,都会有一个唯一标识——run ID(运行ID)。当Slave第一次与Master建立同步时,Master会将自身的运行ID发送给Slave。Slave会把这个Master的run ID保存、记录下来。这样当Slave断线、重连后,会将之前保存的Master的run ID再次发送给目前连接到的Master
-
如果Master发现从Slave处接收到的run ID就是自己本身,那说明这个Slave在断开之前连的就是自己。这样Master就会继续尝试执行部分重同步操作 -
反之则说明,Slave在断开之前连接的Master 与 当前正在连接的Master 并不是同一个。此时,Master就必须对Slave进行完整重同步
PSYNC命令执行流程
对于Slave而言:
-
如果Slave此前未进行过任何复制操作 或 已经执行过了SLAVEOF NO ONE命令,那么Slave在开始一次新的复制时将会向Master发送PSYNC ? -1命令,主动请求Master进行完整重同步 -
反之,Slave此前已经复制过了某个Master。那么Slave在开始复制时会向Master发送PSYNC 命令。其中,runid是Slave上次复制的Master的run ID;offset则是Slave的复制偏移量。Master会根据上述两个参数的情况来决定具体执行何种同步。即是完整重同步、还是部分重同步
对于Master而言,其在接收到Slave的PSYNC命令,作出以下三种回复中的一种
-
「FULLRESYNC」 :意为Master将会与Slave进行完整重同步。其中,runid是Master的运行ID。Slave会将其保存、记录下来。以便下一次发送PSYNC命令时使用;offset则是Master当前的复制偏移量,Slave会将该值用于初始化自身的复制偏移量 -
「+CONTINUE」 :意为Master将会与Slave进行部分重同步。此时Slave只需等待Master将自身所缺失的部分数据发送过来即可 -
「-ERR」:意为Master版本低于2.8,无法识别PSYNC命令。此时Slave会发送SYNC命令,并与Master进行完整重同步
心跳检测机制
与此同时在Redis 2.8版本中也引入了心跳机制。即在命令传播阶段,Slave默认会以每秒一次的频率向Slave发送如下命令。其中offset是Slave的复制偏移量
REPLCONF ACK <offset>
即所谓的心跳机制,其意义有以下几个方面:
「1. 检查主从之间的网络状态」
Master如果长时间没有接收到某个Slave发送的REPLCONF ACK命令,那么Master就知道其与该Slave的网络连接存在异常
「2. 用于实现min-slaves配置项」
Redis的配置文件提供了min-slaves配置项以防止Master在不安全的情况下执行写命令,示例配置如下所示。如果期望禁用该功能,只需将min-slaves-to-write、min-slaves-max-lag任意一项设为0即可
# Master 在接受客户端写入命令时,所必需的可用Slave的最小数目要求
# 一旦可用Slave的数量低于该配置项值,则Master会拒绝执行写命令
min-slaves-to-write 3
# Master与Slave 之间网络延迟(lag)的上限,Unit:秒
# 一旦某个Slave与Master的延迟超过该值,则Master会该Slave将视为不可用状态
min-slaves-max-lag 10
「3. 检测命令丢失」
Master传播给Slave的命令可能会由于网络原因导致Slave并未收到。而Slave通过REPLCONF ACK命令主动向Master上报自身当前的复制偏移量。这样Master即可主动发现传播的命令是否发生了丢失,并利用复制积压缓冲区对丢失的命令进行补传。由于REPLCONF ACK命令、复制积压缓冲区都是2.8版本开始引入的。故在之前的版本中。即使命令在传播过程中丢失了,Master、Slave均无法注意到。更无法进行命令补传
参考文献
-
Redis设计与实现 黄健宏著
原文始发于微信公众号(青灯抽丝):浅谈Redis Replication复制基本原理
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/156602.html