复制
新版复制功能的实现
为了解决旧版复制功能在处理断线重复制情况时的低效问题,Redis从2.8版本开始,使用PSYNC命令代替SYNC命令来执行复制时的同步操作。
PYSNC命令具有完整重同步(full resynchronization)和部分重同步(partial resynchronization)两种模式:
- 1.其中完整重同步用于处理初次复制情况:完整重同步的执行步骤和SYNC命令的执行步骤基本一样,它们都是通过让主服务器创建并发送RDB文件,以及向从服务器发送保存在缓冲区里面的写命令来进行同步
- 2.而部分重同步则用于处理断线后重复制情况:当从服务器在断线后重新连接主服务器时,如果条件允许,主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只要接收并执行这些写命令,就可以将数据库更新至主服务器当前所处的状态。
PSYNC命令的部分重同步模式解决了旧版复制功能在处理断线后重复制时出现的低效情况,
例子
- 举个例子,如表所示,展示了如何使用SYNC命令高效地处理断线后复制情况,对比以下SYNC命令和PSYNC命令处理断线重复制的方法,不难看出,虽然SYNC命令和PSYNC命令都可以让断线的主从服务器重新回到一致状态,但执行部分重同步所需的资源比起执行SYNC命令所需的资源要少得多,完成同步的速度也快得多。执行SYNC命令需要生成、传送和载入整个RDB文件,而部分重同步只需要将服务器缺少的写命令发送给从服务器执行就可以了。
如图所示,展示了主从服务器在执行部分重同步时的通信过程。
部分重同步的实现
部分重同步功能由以下三个部分构成:
- 1.主服务器的复制偏移量(replication offset)和从服务器的复制偏移量
- 2.主服务器的复制积压缓冲区(replication backlog)
- 3.服务器的运行ID(run ID)
复制偏移量
执行复制的双方——主服务器和从服务器会分别维护一个复制偏移量:
- 1.主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量的值加上N
- 2.从服务器每次收到主服务器传播来的N个字节的数据时,就将自己的复制偏移量的值加上N
通过对比主从服务器的复制偏移量,程序可以很容易地直到主从服务器是否处于一致状态:
- 1.如果主从服务器处于一致状态,那么主从服务器两者的偏移量总是相同的
- 2.相反,如果主从服务器两者的偏移量并不相同,那么说明主从服务器并未处于一致状态
例子
- 举个例子。主从服务器的复制偏移量的值都为10086。
如果这时主服务器向三个从服务器传播长度为33字节的数据,那么主服务器的复制偏移量将更新为10086+33=10119,而三个服务器在接收到主服务器传播的数据之后,也会将偏移量更新为10119,如图所示
- 举个例子,如果在向从服务器传播33字节数据之前,上图中的从服务器A断线了,那么主服务器传播的数据将只有从服务器B和从服务器C能收到,在这之后,主服务器、从服务器B和从服务器C三个服务器的
复制偏移量都将更新为10119,而断线的从服务器A的复制偏移量仍然停留在10086,着说明从服务器A与主服务器并不一致,如图所示.
假设从服务器A在断线之后就立即重新连接主服务器,并且成功,那么接下来,从服务器将向主服务器发送PSYNC命令,报告从服务器A当前的复制偏移量为10086,那么这时,主服务器应该对从服务器执行完整
重同步还是部分重同步呢?如果执行部分重同步的话,主服务器又如何补偿从服务器A在断线期间丢失的那部分数据呢?以上问题的答案都和复制积压缓冲区有关。
复制积压缓冲区
复制积压缓冲区是由主服务器维护的一个固定长度(fixed-size)的先进先出(FIFO)队列,默认大小为1MB。
当主服务器进行命令传播时,它不仅会将写命令发送给所有从服务器,还会将写命令入队到复制积压缓冲区里面,如图所示。
因此主服务器的复制积压缓冲区里面会保存着一部分最近传播的写命令,并且复制积积压缓冲区会为队列中的每隔字节记录相应的复制偏移量,如表所示
当从服务器重新连上主服务器时,从服务器会通过PSYNC命令将自己的复制偏移量offset发送给主服务器,主服务器会根据这个复制偏移量来决定对从服务器执行何种同步操作:
- 1.如果offset偏移量之后的数据(也即是偏移量offset+1开始的数据)仍然存在于复制积压缓冲区里面,那么主服务器对从服务器执行部分重同步操作
- 2.相反,如果offset偏移量之后的数据已经不存在于复制积压缓冲区,那么主服务器将对从服务器执行完整重同步操作
固定长度先进先出队列
固定长度先进先出队列的入队和出队规则跟普通的先进先出队列一样:新元素从一边进入队列,而旧元素从另一边弹出队列。和普通先进先出队列随着元素的增加和减少而动态调整长度不同,固定长度先进先出队列的长度是固定的,当入队元素的数量大于队列长度时,最先入队的元素会被弹出,而新元素会被放入队列。
举个例子。如果我们要将’h’、‘e’、‘l’、‘l’、‘o’五个字符放进一个长度为3的
固定长度先进先出队列里面,那么’h’、‘e’、'l’三个字符将首先被放入队列:
[‘h’、‘e’、‘l’]
但是当后一个’l’字符要进入队列时,队首的’h’字符将被弹出,队列变成:
[‘e’、‘l’、‘l’]
接着[‘l’、‘l’、‘o’]
例子
举个例子。如上图中的断线后重连接:
- 1.当从服务器A断线之后,它立即重新连接主服务器,并向主服务器发送PSYNC命令,报告自己的复制偏移量为10086.
- 2.主服务器收到从服务器发来的PSYNC命令以及偏移量10086之后,主服务器将检查偏移量10086之后的数据是否存在于复制积压缓冲区里面,结果发现这些数据仍然存在,于是主服务器向从服务器发送+CONTINUE
回复,表示数据同步将以部分重同步模式来进行 - 3.接着主服务器会将复制积压缓冲区10086偏移量之后的所有数据(偏移量为10087至10119)都发送给从服务器
- 4.从服务器只要接收这33字节的缺失数据,就可以回到与主服务器一致的状态。如图所示
根据需要调整复制积压缓冲区的大小
Redis为复制积压缓冲区设置的默认大小为1MB,如果主服务器需要执行大量写命令,又或者主从服务器断线后重连接所需的时间比较长,那么这个大小也许并不合适。如果复制积压缓冲区的大小设置得不恰当,那么PSYNC命令得复制重同步模式就不能正常发挥作用,因此,正确估算和设置复制积压缓冲区的大小非常重要。复制积压缓冲区的最小大小可以根据公式seoncd * write_size_per_second
来估算:
- 1.其中second为从服务器断线后重新连接上主服务器所需的平均时间(以秒计算)
- 2.而write_size_per_second则是主服务器平均每秒产生的写命令数据量(协议格式的写命令的长度总和)
例如,如果主服务器平均每秒产生1MB的写数据,而从服务器断线之后平均要5秒才能重新连接上服务器,那么复制积压缓冲区的大小就不能低于5MB.为了安全期间,可以将复制积压缓冲区的大小设为2 * second * write_size_per_second,这样可以保证绝大部分断线情况都能用部分重同步来处理,至于复制积压缓冲区大小的修改方法,可以参考配置文件中关于
repl-backlog-size