基于长连接的命令传播
65 哥:完成全量同步后,正常运行过程如何同步呢?
当主从库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播,使用长连接的目的就是避免频繁建立连接导致的开销。
在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING 和 REPLCONF ACK。
主->从:PING
每隔指定的时间,主节点会向从节点发送 PING 命令,这个 PING 命令的作用,主要是为了让从节点进行超时判断。
从->主:REPLCONF ACK
在命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令:
REPLCONF ACK <replication_offset>
其中 replication_offset 是从服务器当前的复制偏移量。发送 REPLCONF ACK 命令对于主从服务器有三个作用:
- 检测主从服务器的网络连接状态。
- 辅助实现 min-slaves 选项。
- 检测命令丢失, 从节点发送了自身的 slave_replication_offset,主节点会用自己的 master_replication_offset 对比,如果从节点数据缺失,主节点会从
repl_backlog_buffer
缓冲区中找到并推送缺失的数据。
注意,offset 和 repl_backlog_buffer 缓冲区,不仅可以用于部分复制,也可以用于处理命令丢失等情形;区别在于前者是在断线重连后进行的,而后者是在主从节点没有断线的情况下进行的。
如何确定执行全量同步还是部分同步?
在 Redis 2.8 及以后,从节点可以发送 psync 命令请求同步数据,此时根据主从节点当前状态的不同,同步方式可能是全量复制或部分复制。本文以 Redis 2.8 及之后的版本为例。
关键就是 psync
的执行:
- 从节点根据当前状态,发送
psync
命令给 master:
- 如果从节点从未执行过
replicaof
,则从节点发送psync ? -1
,向主节点发送全量复制请求;
- 如果从节点之前执行过
replicaof
则发送psync <runID> <offset>
, runID 是上次复制保存的主节点 runID,offset 是上次复制截至时从节点保存的复制偏移量。
- 主节点根据接受到的
psync
命令和当前服务器状态,决定执行全量复制还是部分复制:
- runID 与从节点发送的 runID 相同,且从节点发送的
slave_repl_offset
之后的数据在repl_backlog_buffer
缓冲区中都存在,则回复CONTINUE
,表示将进行部分复制,从节点等待主节点发送其缺少的数据即可;
- runID 与从节点发送的 runID 不同,或者从节点发送的 slave_repl_offset 之后的数据已不在主节点的
repl_backlog_buffer
缓冲区中 (在队列中被挤出了),则回复从节点FULLRESYNC <runid> <offset>
,表示要进行全量复制,其中 runID 表示主节点当前的 runID,offset 表示主节点当前的 offset,从节点保存这两个值,以备使用。
一个从库如果和主库断连时间过长,造成它在主库 repl_backlog_buffer
的 slave_repl_offset 位置上的数据已经被覆盖掉了,此时从库和主库间将进行全量复制。
总结下
每个从库会记录自己的 slave_repl_offset
,每个从库的复制进度也不一定相同。
在和主库重连进行恢复时,从库会通过 psync 命令把自己记录的 slave_repl_offset
发给主库,主库会根据从库各自的复制进度,来决定这个从库可以进行增量复制,还是全量复制。
replication buffer 和 repl_backlog
- replication buffer 对应于每个 slave,通过
config set client-output-buffer-limit slave
设置。
repl_backlog_buffer
是一个环形缓冲区,整个 master 进程中只会存在一个,所有的 slave 公用。repl_backlog 的大小通过 repl-backlog-size 参数设置,默认大小是 1M,其大小可以根据每秒产生的命令、(master 执行 rdb bgsave) +( master 发送 rdb 到 slave) + (slave load rdb 文件)时间之和来估算积压缓冲区的大小,repl-backlog-size 值不小于这两者的乘积。
总的来说,replication buffer
是主从库在进行全量复制时,主库上用于和从库连接的客户端的 buffer,而 repl_backlog_buffer
是为了支持从库增量复制,主库上用于持续保存写操作的一块专用 buffer。
repl_backlog_buffer
是一块专用 buffer,在 Redis 服务器启动后,开始一直接收写操作命令,这是所有从库共享的。主库和从库会各自记录自己的复制进度,所以,不同的从库在进行恢复时,会把自己的复制进度(slave_repl_offset
)发给主库,主库就可以和它独立同步。
如图所示:
4. 主从应用问题
4.1 读写分离的问题
数据过期问题
65 哥:主从复制的场景下,从节点会删除过期数据么?
这个问题问得好,为了主从节点的数据一致性,从节点不会主动删除数据。我们知道 Redis 有两种删除策略:
- 惰性删除:当客户端查询对应的数据时,Redis 判断该数据是否过期,过期则删除。
- 定期删除:Redis 通过定时任务删除过期数据。
65 哥:那客户端通过从节点读取数据会不会读取到过期数据?
Redis 3.2 开始,通过从节点读取数据时,先判断数据是否已过期。如果过期则不返回客户端,并且删除数据。
4.2 单机内存大小限制
如果 Redis 单机内存达到 10GB,一个从节点的同步时间在几分钟的级别;如果从节点较多,恢复的速度会更慢。如果系统的读负载很高,而这段时间从节点无法提供服务,会对系统造成很大的压力。
如果数据量过大,全量复制阶段主节点 fork + 保存 RDB 文件耗时过大,从节点长时间接收不到数据触发超时,主从节点的数据同步同样可能陷入全量复制->超时导致复制中断->重连->全量复制->超时导致复制中断……的循环。
此外,主节点单机内存除了绝对量不能太大,其占用主机内存的比例也不应过大:最好只使用 50% - 65% 的内存,留下 30%-45% 的内存用于执行 bgsave 命令和创建复制缓冲区等。
总结
- 主从复制的作用:AOF 和 RDB 二进制文件保证了宕机快速恢复数据,尽可能的防止丢失数据。但是宕机后依然无法提供服务,所以便演化出主从架构、读写分离。
- 主从复制原理:连接建立阶段、数据同步阶段、命令传播阶段;数据同步阶段又分为 全量复制和部分复制;命令传播阶段主从节点之间有 PING 和 REPLCONF ACK 命令互相进行心跳检测。
- 主从复制虽然解决或缓解了数据冗余、故障恢复、读负载均衡等问题,但其缺陷仍很明显:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制;这些问题的解决,需要哨兵和集群的帮助,我将在后面的文章中介绍,欢迎关注。