复制简介
数据的同步过程一般都涉及到全量数据的迁移以及后续增量数据的同步。
- 对于Mysql数据库可以通过mysqldump+binlog的方式获取全量+增量数据;
- 对于MongoDB数据库可以通过dump+oplog的方式获取全量+增量数据;
- 对于PostgreSQL数据库,可以通过 dump+WAL日志的方式获取全量+增量数据。
- 同样,对于Redis数据库,可以通过RDB文件存储全量数据,并采用复制偏移量(+复制积压缓冲区)的方式实现了增量数据的同步。其中RDB文件是将全量数据以序列化的方式持久化到.rdb文件中,存放的是二进制数据(序列化后的数据),并在从库请求复制后将其发送给从库;复制偏移量分别在主库和从库记录了已经应用的数据,用以判断主库和从库数据是否一致以及数据相差了多少(在Redis2.8版本之后,主库在将数据发送给从库的同时,还会将复制偏移量写入到复制积压缓冲区)。
SYNC同步
在主Master接收到SYNC命令之后,它会执行bgsave在后台生成一个RDB文件,并且使用一个缓冲区记录从现在开始执行所有写命令。当bgsave生成的RDB文件完成了之后,它就发送给从服务器去进行载入。在更新状态完成之后,Master再将记录在缓冲区里面的新命令发送给从服务器,这样从服务器进行执行,主从服务器就保持了一致状态。
从服务器到主服务器的复制可以分为两种情况:
- 初次复制
- 断链后的重复制
初次复制就是进行建立连接,然后进行全量和增量的同步,它的SYNC命令可以很好地完成任务。 但对于断线后的重复制,处于命令传播阶段的主从服务器因为网络原因而中断又重连,会再次发送SYNC命令做全量+增量同步效率较低。 SYNC命令是一个非常耗费资源的操作,资源包括CPU、内存、磁盘、带宽、流量等。
PSYNC同步
为了解决SYNC在处理断线重复制时候的低效问题,Redis从2.8版本之后开始使用PSYNC命 令,它支持完整重同步和部分重同步。 完整重同步和SYNC一样,部分重同步就是在处理断 线重新连接之后,主节点只向从节点发送链接断开期间的写命令,它的实现基于以下三部分:
- 复制偏移量(replication offset)
Master:每次向Slave发送n个字节数据时,就会将自己的offset+n;
Slave:每次收到Master发送来的n个字节数据时,就会将自己的offset+n;
如果主从服务器处于一致状态,那么Master和Slave的Offset总是相同的。 - 复制积压缓冲区(replication backlog)
Master上维护的每一个固定大小(fixed-size)的FIFO队列,保存着一部分最近传播的写命令。
Master进行命令传播时,不仅会将命令发送给所有Slave,还会将写命令入队到复制积压缓 冲区。
复制积压缓冲区会为队列中的每个字节记录相应的复制偏移量,Slave重连Master时,会通过PSYNC命令将自己的Offset发送给Master。它是一个环形缓冲区,大小是固定的,可存储的命令有限,超出部分将会被删除。 - Replication ID(复制ID)
每个Redis的主节点都用一个随机生成的字符串来表示在某一时刻其内部存储数据的状态, “某一时刻”可以理解为其成为master角色的那一刻,在第一个从节点加入时,Redis初始化了复制ID。 - 如果Slave的Offset与Master的Offset不相等,并且Slave的Offset偏移量之后的数据仍存在于replication backlog中,那么Master将对Slave执行部分重同步操作;
- 否则,需要执行完整重同步操作。
缺点:
- 在链式的一主两从的结构中,M -> S1-> S2,如果S1下线了,那么S2在成为M的从库后,会进行完全重同步;
- 在树状的一主两从的结构中,S1 -> M <- S2,如果M不可用下线,S1提升为主,那 么S2在成为S1的从库后,会进行完全重同步;
- 在一主一从的结构中,M -> S, 发生主从切换,需要进行完全重同步;
- 在从实例发生重启,及时不变更主从关系,由于丢失了所有的复制信息,还是会进行完全重同步;
PYSNC2
- 支持实例重启后的部分重同步
- 支持主从切换后的部分重同步
在建立主从复制关系时,master会将自己的replid传递给slave,slave会把自己的replid更新为master的,这样逐级传递下去。最终master、slave1、slave2的server.replid全部一样,都是master的replid。主从复制建立完成之后,这条复制链上所有的数据都由master产生,也就是说master、 slave1、slave2的offset也全都匹配得上。
优化解决: - 对于场景1: M -> S1-> S2
可以看出,M和S2具有相同的replid,并且offset也可以匹配上,此时就可以直接进行部分重同步,避免了完全同步的开销; - 对于场景2: S1 -> M <- S2
当S1变更为主时,M的replid和offset信息并不会被丢去,而是被保存在了S1的replid2和 second_repl_offset,参与同步;这样在S2在成为S1的从库之后,依然能够查到同步之前的同步信息,从而可以进行部分重同步,不需要进行完全重同步。 - 对于场景3: M -> S
当S变更为主时,原来master的replid,offset不会丢弃,而是会保存在replid2和 second_repl_offset中,参与匹配。那么M的replid,offset都能从新主中找到replid2,并且和新主的second_repl_offset也匹配的上,就可以直接进行增量同步,避免全量同步的开销。 - 对于场景4: 从实例重启
psync2在意外关闭前会调用 rdbSaveInfoAuxFields 函数把当前的复制 ID(即关闭前正在复制的 master 的 replid,因为 slave 中的 replid 字段保存的是 master 的复制 ID) 和复制偏移量一起保存到 RDB 文件中,后面该 slave 重启的时候,就可以从 RDB 文件中读取复制 ID 和复制偏移量,然后重连上 master 后 slave 将这两个值发送给master。
注:上述所有场景的前提是数据依然保存在backlog中,否则还是会进行完全重同步。
Redis命令传播原理
- 当完成同步操作之后,master-slave便会进入命令传播阶段,此时master-slave的数据是一致的。
- 当maste执行完新的写命令后,会通过传播程序把该命令追加至复制积压缓冲区,然后异步地发送给slave。
-
slave接收命令并执行,同时更新slave维护的复制偏移量offset。
主从数据一致性
如果slave可以收到每条传播指令,并执行成功,便可以保持与master的数据一致状态。但是master并不等待slave节点的返回,master与slave是通过网络通信,由于网络抖动等因 素,命令传播过程不保证slave真正接收到,那如何在传播阶段确保主从数据一致呢?
在命令传播阶段,每隔一秒slave节点向master节点发送一次心跳信息,命令格式为 REPLCONF ACK <offset>。其中offset指从节点保存的复制偏移量。REPLCONF ACK命令的作用包括:
- 实时监测主从节点网络状态
该命令会被主节点用于复制超时的判断。此外在主节点中使用info Replication,可以看到其从节点的状态中的lag值,代表的是主节点上次收到该 REPLCONF ACK命令的时间间隔,在正常情况下,该值应该是0或1。 - 检测命令丢失
从节点发送了自身的offset,主节点会与自己的offset对比,如果从节点数据缺失(如网络丢包),主节点会推送缺失的数据(这里也会利用复制积压缓冲区)。 注意,offset和复制积压缓冲区,不仅可以用于部分复制,也可以用于处理命令丢失等情 形;区别在于前者是在断线重连后进行的,而后者是在主从节点没有断线的情况下进行的。 - 辅助保证从节点的数量和延迟
Redis主节点中使用min-slaves-to-write和min-slaves-max-lag参数,来保证主节点在不安全的情况下不会执行写命令;所谓不安全,是指从节点数量太少,或延迟过高。例如min-slaves-to-write和min-slaves-max-lag分别是3和10,含义是如果从节点数量小于3个,或所有从节点的延迟值都大于10s,则主节点拒绝执行写命令。 而这里从节点延迟值的获取,就是通过主节点接收到REPLCONF ACK命令的时间来判断的,即前面所说的info Replication中的lag值。
全量同步和增量同步
在全量复制阶段,主节点会将执行的写命令放到复制缓冲区中,该缓冲区存放的数据包括了以下几个时间段内主节点执行的写命令:bgsave生成RDB文件、RDB文件由主节点发往从 节点、从节点清空老数据并载入RDB文件中的数据。当主节点数据量较大,或者主从节点之间网络延迟较大时,可能导致该缓冲区的大小超过了限制,此时主节点会断开与从节点之间的连接;这种情况可能引起全量复制→复制缓冲区溢出导致连接中断→重连→全量复制→复制缓冲区溢出导致连接中断......的循环。
复制缓冲区的大小由client-output-buffer-limit slave{hard limit}{soft limit}{soft seconds}配 置,默认值为client-output-buffer-limit slave 256MB 64MB 60,其含义是:如果buffer大于 256MB,或者连续60s大于64MB,则主节点会断开与该从节点的连接。该参数是可以通过 config set命令动态配置的(即不重启Redis也可以生效)。
backlog
Redis为复制积压缓冲区设置的默认大小为1MB,如果主服务器需要执行大量写命令,又或者主从服务器断线后重连接所需的时间比较⻓,那么这个大小也许并不合适。如果复制积压 缓冲区的大小设置得不恰当,那么PSYNC命令的复制重同步模式就不能正常发挥作用,正确估算和设置复制积压缓冲区的大小非常重要。
复制积压缓冲区的最小大小可以根据公式second*write_size_per_second 来估算:
- 其中second为从服务器断线后重新连接上主服务器所需的平均时间(以秒计算)。
- 而write_size_per_second则是主服务器平均每秒产生的写命令数据量(协议格式的写命令的⻓度总和)。
例如,如果主服务器平均每秒产生1 MB的写数据,而从服务器断线之后平均要5秒才能重新 连接上主服务器,那么复制积压缓冲区的大小就不能低于5MB。
为了安全起⻅可以将复制积压缓冲区的大小设为2secondwrite_size_per_second,这样可以保证绝大部分断线情况都能用部分重同步来处理。 至于复制积压缓冲区大小的修改方法,可以参考配置文件中关于repl-backlog-size选项的说明。