redis高可用 - 主从复制
工作需要,调研了一下redis的复制实现。在2.8版本之前和之后,复制方式有所不同。2.8之前的复制方式对于初次复制数据没有问题,对于断连接重新复制比较耗性能,因为都是全量复制。2.8之后对断线重连做了优化,采用差量复制。
旧版复制功能的实现
redis复制功能有同步和命令传播两种。
同步操作将从服务器的数据库状态更新至主服务器当前的数据库状态;
命令传播用于在主服务器数据库状态改变,主从服务器数据状态不一致时,让主从服务器数据库状态重新回到一致状态。
同步
客户端向从服务器发送SLAVEOF命令,从服务器开始向主服务器请求同步数据,通过向主服务器发送SYNC命令完成同步过程,下面罗列SYNC命令的执行步骤:
1.从服务器向主服务器发送SYNC命令;
2.主服务器收到SYNC命令后执行BGSAVE命令,在后台生成一个rdb文件,并使用一个缓冲区记录从此刻开始执行的所有写操作命令;
3.主服务器的BGSAVE命令执行完毕,主服务器会将生成的rdb文件发送给从服务器,从服务器接收并加载这个rdb文件,将自己的数据库状态更新至主服务器执行BGSAVE命令时的数据库状态;
4.主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些写命令,将自己的数据库状态更新至主服务器数据库当前所处的状态。
时间 | 主服务器 | 从服务器 |
t0 | 服务器启动 | 服务器启动 |
t1 | 执行SET K1 V1 | |
t2 | 执行SET K2 V2 | |
t3 | 执行SET K3 V3 | |
t4 | 向主服务器发送SYNC命令 | |
t5 |
接收从服务器发来的SYNC命令,执行BGSAVE命令,创建包含K1,K2,K3的rdb文件,并用缓冲区记录接下来执行的所有写操作 | |
t6 | 执行SET K4 V4,并将这个命令保存到缓冲区 | |
t7 | 执行SET K5 K5,并将这个命令保存到缓冲区 | |
t8 | BGSAVE命令执行完毕,向从服务器发送rdb文件 | |
t9 |
接收并加载rdb文件,使数据库更新到主服务器执行BGSAVE命令时的数据库状态 | |
t10 | 向从服务器发送缓冲区保存的写命令SET K4 V4和SET K5 V5 | |
t11 | 接收并执行主服务器发来的两个SET命令 | |
t12 | 同步完成,现在注册服务器状态一致 | 同步完成,现在注册服务器状态一致 |
命令传播
同步操作完成之后,主从服务器数据库状态达到一致,后续的状态就靠命令传播保持主从服务器状态一致。主服务器会将自己执行的写操作命令,原封不动地发送给从服务器,从服务器执行相同的写操作之后,主从服务器数据库状态重新达到一致。
旧版本复制功能的缺陷
在redis2.8之前,从服务器对主服务器的复制分为以下两种情况:
1.初次复制:从服务器没有复制过任何主服务器,或者从服务器要复制的主服务器和上次复制的主服务器不同;
2.断线重连重新复制:处于命令传播的主从服务器因为网络原因而中断了复制,但从服务器通过自动重连重新连接上了主服务器,并重新从头开始全量复制主服务器。
对于初次复制而言,旧版复制功能可以较好的完成任务,但对于断线重连的情况,虽然可以让主从服务器重新回到一致,但是效率确是比较低下的。
SYNC操作是一个非常耗费资源的操作
每次执行SYNC命令时,主从服务器将执行以下动作:
1.主服务器执行BGSAVE生成rdb文件,此操作耗费主服务器大量的cpu,内存和磁盘io资源。
2.主服务器要将自己生成的rdb文件发送给从服务器,此操作会消耗主从服务器大量的网络资源,并对主服务器响应命令请求的时间产生影响。
3.接收到rdb文件的从服务器需要载入rdb文件,并且载入期间,从服务器因阻塞而无法处理命令请求。
新版复制功能的实现
为了解决旧版断线重连重复复制的低效问题,2.8版本开始使用PSYNC代替SYNC命令执行同步操作。PSYNC命令包括完整同步和部分同步:
1.完整同步用于处理初次复制的情况:PSYNC执行步骤和SYNC的执行步骤基本一样;
2.部分同步用于处理断线重连的复制情况:断线重连时,如果条件允许,主服务器可以将主从服务器断连接期间执行的写命令发送给从服务器,从服务器更具这些写命令更新数据库状态至主服务器当前的状态。
下表展示PSYNC命令解决断线重连的复制情况的例子:
时间 | 主服务器 | 从服务器 |
t1 | 主从服务器完成同步 | 主从服务器完成同步 |
t2 | 执行并传播SET K2 V2 | 执行主服务器传播来的SET K2 V2 |
t3 | 执行并传播SET K3 V3 | 执行主服务器传播来的SET K3 V3 |
… | ||
t20 | 执行并传播SET K20 V20 | 执行并传播SET K20 V20 |
t21 | 主从服务器断开连接 | 主从服务器断开连接 |
t22 | 执行SET K22 V22 | 断线,尝试重连 |
t23 | 执行SET K23 V23 | 断线,尝试重连 |
t24 | 主从服务器重新建立连接 | 主从服务器重新建立连接 |
t25 | 向主服务器发生PSYNC命令 | |
t26 | 向从服务器返回+CONTINUE回复,标识执行部分同步 | |
t27 | 接收主服务回复的+CONTINUE,准备部分同步 | |
t28 | 向从服务器发送SET K22 V22和SET K23 V23两个命令 | |
t29 | 接收并执行主服务器传来的两个命令 | |
t30 | 主从服务器再次完成同步,状态一致 | 主从服务器再次完成同步,状态一致 |
由此可见,PSYNC在执行部分同步的时候比执行SYNC命令所需要的资源少很多,速度也加快了。执行SYNC命令需要生成,传送,载入整个rdb文件,而PSYNC的部分同步只需将差量的写命令发送给从服务器便可。
部分重同步的实现
部分重同步由以下三部分组成:
1.主服务器的复制偏移量和从服务器的复制偏移量;
2.主服务器的复制积压缓冲区;
3.服务运行的id。
复制偏移量
执行复制的双方分别维护一个复制偏移量:
1.主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量加N;
2.从服务器每次收到主服务器传来的N个字节的数据时,就将自己的复制偏移量加N。
如果主从服务器数据库处于一致状态时,那么主从服务器的复制偏移量总是一致的,但是如果主从服务器偏移量不相同,说明主从服务器状态不一致。
复制积压缓冲区
复制积压缓冲区是由主服务器维护的一个固定长度的先进先出队列,默认大小为1MB。
当主服务器进行命令传播时,他会做两个操作,1.将命令发送给从服务器,2.将命令写入到复制积压缓冲区,因此复制积压缓冲区里会保存一部分最近传播的写命令,并且会为队列中的每个字节记录相应的复制偏移量,如下表所示。
偏移量 | … | 10086 | 10087 | 10088 | 10089 | 10090 | 10091 | 10092 | 10093 | 10094 | 10095 | 10096 | … |
字节值 | … | ‘*’ | 3 | ‘\r’ | ‘\n’ | ‘$’ | 3 | ‘\r’ | ‘\n’ | ‘S’ | ‘E’ | ‘T’ | … |
当主从服务器重连成功后,从服务器会通过PSYNC命令将自己的复制偏移量offset发送给主服务器,主服务器根据这个偏移量决定对从服务器执行何种操作:
1.如果offset偏移量之后的数据仍然存在于复制积压缓冲区里面,那么主服务器会对从服务器进行部分同步操作;
2.如果offset偏移量之后的数据不在复制积压缓存区里面,那么主服务器对从服务器进行完整同步操作。
服务器运行id
每个redis服务器,无论主从服务,都会有自己的运行id。运行id在启动时生成,由40个随机的十六进制字符组成。
当从服务器对主服务器进行初次复制时,主服务器将自己运行的id传送给从服务器,从服务器将这个id保存。
当从服务器断线并重连上上一个主服务器时,从服务器将保存的id发送给主服务器:
如果从服务器保存的运行id和当前连接的主服务运行id相同,说明从服务器断线之前复制的就是这台主服务器,主服务器可以继续吃、执行部分同步操作;
如果从服务器保存的运行id和当前连接的主服务器运行id不一致,那么说明从服务器断线之前连接的主服务器不是当前连接的主服务器民主服务器对从服务器执行完整同步操作。
总结
新redis的复制机制在老的复制机制上加了个部分同步,解决断线重连时的全量复制数据的低性能情况。部分同步的机制依赖于复制偏移量,复制积压缓冲区,服务运行id。
复制偏移量是为了让从服务器知晓下次该从何处开始进行部分重同步;
复制积压缓冲区是为了主服务器找到上次复制的点,从该点开始给从服务器复制数据。如果找不到这个点,就要执行完整同步过程了;
服务运行id主要是为保证重连的主服务器是否为上次连接的主服务器,如果是才能执行部分重同步,否则就只能进行完整同步了。
本文参考黄健宏著的 redis设计与实现