就是从库和主库的数据要保持一致,主库的数据同步到从库的过程。
现有两个redis
- 127.0.0.1:6379
- 127.0.0.1:12345
127.0.0.1:12345 > slaveof 127.0.0.1:6379
这个时候,12345就是6379的从库了。
复制分为全量复制和增量复制。
全部复制触发在从库初始化的时候。
其他情况都是走的增量复制。增量就是主库每次修改的数据。
2.8之前的复制功能
redis复制功能分为同步(sync)和命令传播(command propagate)
- 同步操作用于将从服务器,更新至与主服务器一致
- 命令传播则用于,在主服务器被修改,导致主从不一致的时候,让主从重新回到一致的动作。
同步
当从服务器知道自己是从服务器,并初始化的时候,开始执行同步。
过程:
- 从服务器想主服务器发送SYNC命令
- 收到SYNC命令的主服务器,执行BGSAVE命令,在后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令。
- 当主服务的BGSAVE命令执行完毕时,主服务器会将刚生成的RDB文件发送给从服务器。从服务器接收并载入这个RDB文件,将自己的数据更新至与主服务器执行BGSAVE命令时的状态。
- 主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行这些命令,将自己的数据库状态更新至主服务器当前所处的状态。
时间 | 主服务器 | 从服务器 |
---|---|---|
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 v5,并将这个命令记录到缓冲区 | |
T8 | BGSAVE命令执行完毕,向从服务器发送RDB文件 | |
T9 | 接收并载入主服务器发来的RDB文件,获得k1,k2,k3三个键 | |
T10 | 向从服务器发送缓冲分区保存的写命令set k4 v4和set k5 v5 | |
T11 | 接收并执行主服务器发来的两个set命令,得到k4,k5两个键 | |
T12 | 同步完成,现在主从服务器两者的数据库都包含了键k1,k2,k3,k4,k5 | 同步完成,现在主从服务器两者的数据库都包含了键k1,k2,k3,k4,k5 |
命令传播
当复制执行完了,以后主库的修改怎么做到的主从一致呢,这就要用到命令传播了。
当主库删除了k3,让从库也得删除k3。就要把del k3这个命令发送给从服务器。从服务器收到后,执行,这样就又一致了。完事。
老版本的缺陷
- 初次同步。从服务器以前没有复制过任何主服务器,或者从服务器当前要复制的主服务器和上一次复制的主服务器不同。
- 断线后再次同步。处于命令传播阶段的主从服务器因为网络原因而中断了复制,从服务器通过自动重连,重新连上了主服务器,这时候开始继续复制主服务器。
初次复制。没有问题。问题出在了断线后再次复制。看下过程:
时间 | 主服务器 | 从服务器 |
---|---|---|
T0 | 主从服务器完成同步 | 主从服务器完成同步 |
T1 | 执行 set k1 v1 | 执行主服务器传来的 set k1 v1 |
T2 | 执行 set k2 v2 | 执行主服务器传来的 set k2 v2 |
... | ... | ... |
T10085 | 执行并传播set k10086 v10086 | 执行主服务器传来的 set k10085 v10085 |
T10086 | 执行并传播set k10086 v10086 | 执行主服务器传来的 set k10086 v10086 |
T10087 | 主从服务断开连接 | 主从服务断开连接 |
T10088 | 执行set k10087 v10087 | 断线中,尝试重新连接主服务器 |
T10089 | 执行set k10088 v10088 | 断线中,尝试重新连接主服务器 |
T10090 | 执行set k10089 v10089 | 断线中,尝试重新连接主服务器 |
T10091 | 主从服务器重新连接 | 主从服务器重新连接 |
T10092 | 向主服务器发送SYNC命令 | |
T10093 | 收到从服务器发来的SYNC命令,执行BGSAVE命令,创建包含k1值k10089的RDB文件,并使用缓冲区记录接下来执行的所有写命令 | |
T10094 | BGSAVE执行完毕,向从服务器发送RDB文件 | |
T10095 | 接收并载入主服务器发来的RDB文件,获得k1至k10089 | |
T10096 | 因为BGSAVE命令执行期间,主服务器没有执行写命令,所以不会发送缓冲区的写命令 | |
T10097 | 主从服务器再次完成同步 | 主从服务器再次完成同步 |
问题就在T10093。他把所有的数据都放RDB传给从服务器了。从服务器k1至k10086都是有的。不用在来一遍。所以新版本复制功能就出来的,主要原则是引入了offset。
2.8之后的复制功能
2.8之后的同步命令不是SYNC了,而是PSYNC。
PSYNC分两种:
- 完成重同步。和旧版本SYNC一样,也是全量RDB发送给从库。
- 部分重同步。这就不一样了。只把断线重连这段时间的写命令发送给从服务器,从服务器接收到这些命令,主从就一致了。
时间 | 主服务器 | 从服务器 |
---|---|---|
T0 | 主从服务器完成同步 | 主从服务器完成同步 |
T1 | 执行 set k1 v1 | 执行主服务器传来的 set k1 v1 |
T2 | 执行 set k2 v2 | 执行主服务器传来的 set k2 v2 |
... | ... | ... |
T10085 | 执行并传播set k10086 v10086 | 执行主服务器传来的 set k10085 v10085 |
T10086 | 执行并传播set k10086 v10086 | 执行主服务器传来的 set k10086 v10086 |
T10087 | 主从服务断开连接 | 主从服务断开连接 |
T10088 | 执行set k10087 v10087 | 断线中,尝试重新连接主服务器 |
T10089 | 执行set k10088 v10088 | 断线中,尝试重新连接主服务器 |
T10090 | 执行set k10089 v10089 | 断线中,尝试重新连接主服务器 |
T10091 | 主从服务器重新连接 | 主从服务器重新连接 |
T10092 | 向主服务器发送PSYNC命令 | |
T10093 | 向从服务器返回+continue回复,表示执行部分重同步 | |
T10094 | 接收+continue回复,准备执行部分重同步 | |
T10095 | 向从服务器发送set k10087 v10087、set k10088 v10088、set k10089 v10089三个命令 | |
T10096 | 接收并执行主服务器传来的三个set命令 | |
T10097 | 主从服务器再次完成同步 | 主从服务器再次完成同步 |
部分重同步由三个部分构成
- 主服务和复制偏移量和从服务器的复制偏移量
- 主服务器的复制积压缓冲区
- 服务器运行的ID(run ID)
复制偏移量
一张图你就明白了
复制积压缓冲区
复制积压缓冲区是一个由主服务器维护的一个固定长度,先进先出队列。默认大小1MB。
在命令传播过程中,主库的过程:
当从服务器连接上主服务器时,从服务器会通过PSYNC命令将自己的复制偏移量offset发送给主服务器,主服务器会根据这个复制偏移量来决定对服务器进行何种同步操作:
如果offset偏移量之后的数据,仍然存在于复制积压缓冲区里面,那么主服务器将对从服务器执行部分重同步。
相反,offset之后的数据,不在复制积压缓冲区中(两种情况,1.掉线时间太长。2.换了新的主),那么进行完整重同步。
回到刚刚断线重连表格的那个例子:
当从服务器A重连后,向主服务器发送PSYNC命令,报告自己的复制偏移量为10086
主服务器收到从服务器的PSYNC命令以及10086偏移量之后,主服务器将检查偏移量之后的数据是否还存在与复制积压缓冲区中,结果发现仍然存在,于是想从服务器发送+continue回复,表示以部分同步模式来进行。
接着主服务器会将复制积压缓冲区10086偏移量之后的所有数据(10087至10119)都要发送发给给从服务器。
从服务器接收到这33字节的缺失数据,就可以回到与主服务器一致的状态。
服务器运行ID
不管主从,服务器启动时,都会生成一个ID。
当主从初次复制的时候,从库会保存自己主库的ID。
当从断线并重新连上一个主服务器,从服务器会把上次复制的主服务器ID,发送给当前的主库。主库收到之后会判断:
和自己一样。说明之前就是我的从,可能就会执行部分重同步。(缓冲区没有了就完成重同步了)。
和自己不一样。说明之前是别人的从,第一次成为我的从,必然会执行完整重同步。
如果在传输过程中,数据丢了怎么办
主库给从库发送10087至10119这段偏移量的所有数据,但网络问题丢了。在从库想主库每秒发送心跳的时候,心跳包中会包含从库的offset,如果不一样,把差掉的补发。(2.8之前没有这个功能)。
知识点
当从库接收主库的全量RDB文件。在载入RDB文件的时候,从库是不可用的。