主从全量同步与 repl_backlog 增量复制
主从复制是高可用的地基,而全量同步是它最贵的操作——fork、传 RDB、加载,GB 级实例动辄数分钟。本文拆解全量同步的完整流程,讲清 repl_backlog 环形缓冲区的角色与容量规划。
一、复制流与两个关键游标
主库把每条写命令传播给从库的同时,会追加写入 repl_backlog,并维护一个全局偏移量 master_repl_offset;从库记录自己消费到的偏移量。两者相差多少,就代表从库落后多少:
redis-cli INFO replication
# master_replid:3f2a8c...(40 字符)
# master_repl_offset:88213904
# repl_backlog_active:1
# repl_backlog_size:1048576
# repl_backlog_first_byte_offset:87165329
# repl_backlog_histlen:1048576
repl_backlog 是主库上的环形缓冲区:写满后从头部覆盖,repl_backlog_first_byte_offset 指向缓冲区内最老的字节。从库的偏移量只要不比它小,断线后就能从缓冲区补齐增量,无需全量同步。
二、全量同步的完整流程
从库第一次连接或无法增量续传时,走全量同步:
- 从库发送
PSYNC ? -1,表示从未同步过; - 主库回复
+FULLRESYNC <replid> <offset>,并在本地发起 BGSAVE(有盘写临时 RDB,或无盘直接流入 socket); - 主库把 RDB 传给从库,同时把期间的新写命令暂存到该从库的复制缓冲区;
- 从库清空本地数据,加载 RDB;
- 主库把缓冲区里积压的命令补发给从库;
- 进入命令传播阶段,此后主库每收到写命令就同步给从库。
第 3 步的缓冲区由 client-output-buffer-limit replica 控制,默认 256mb 64mb 60:硬上限 256 MB,或持续 60 秒超过软上限 64 MB 时断开从库。同步期间写入量太大、RDB 传输太慢都会把缓冲区撑爆,全量同步功亏一篑并重新开始。三个数字的含义是"硬上限 + 软上限 + 软上限持续的秒数",判断顺序是先看是否超硬上限(立即断开),再看是否持续超软上限超过指定秒数。
第 4 步"从库清空本地数据"是一个容易被忽略的风险点。执行前从库会调用 emptyDb 清空现有数据集,如果此时从库上恰好有业务读流量(例如被误配为可读副本承载查询),会有一段时间读到空数据。因此新挂从库时应先把它从读写分离的地址列表里摘除,等 master_link_status:up 且增量追平后再放回。
提示:Redis 7 把所有从库的复制输出改为共享缓冲区,N 个从库不再把这块内存放大 N 倍,大批量挂从库时的主库内存压力明显改善。
三、backlog 容量规划
默认 repl-backlog-size 1mb 对生产实例几乎一定不够。规划公式:
backlog 大小 ≥ 平均写入速率(字节/秒) × 预期最长断连时长(秒)
例:峰值写入 20 MB/s,网络抖动按 60 秒容忍,需要 1.2 GB,取整配 2 GB:
repl-backlog-size 2gb
client-output-buffer-limit replica 1gb 256mb 120
backlog 是常驻内存,但一份被所有从库共享,2 GB 换来"绝大多数断线免全量",性价比很高。repl-backlog-ttl 默认 3600,最后一个从库断开 1 小时后释放这块内存;设为 0 表示永不释放,从库会频繁短暂断连的实例建议保持常驻,省去反复分配的开销。
规划时还要注意 repl_backlog_histlen 这个字段。它表示当前缓冲区里实际存了多少字节,正常情况下应等于 repl_backlog_size(满了才会绕回覆盖)。如果 histlen 长期远小于 size,说明写入速率并不高,配置偏大可以适度下调;如果 histlen 始终等于 size 而 first_byte_offset 快速推进,说明缓冲区一直在绕圈,任何稍长的断连都会丢缺口。这个对比比只看配置值更能反映真实水位:
redis-cli INFO replication | grep -E "repl_backlog_(size|first_byte_offset|histlen)"
# repl_backlog_size:2147483648
# repl_backlog_first_byte_offset:30284102
# repl_backlog_histlen:2147483648
上例 histlen 等于 size,缓冲区已经写满并处于持续覆盖状态,此时可支撑的断连时长等于 size / 写入速率。用两次采样相减还能直接算出实际写入速率:
redis-cli INFO replication | grep master_repl_offset
# master_repl_offset:189374982
# 30 秒后再采样一次
# master_repl_offset:190571316
两次相差 1196334 字节、间隔 30 秒,写入速率约 39.9 KB/s。把这个数代入公式,2 GB 的 backlog 可容忍约 15 小时断连——远超实际需要,说明配置偏保守,可以下调到 512 MB 以节省内存。反过来,如果算出只能容忍几十秒,就必须扩容,否则每次网络抖动都会触发全量。
四、观察与验证
- 主库视角:INFO replication 的 slave0 行能看到每个从库的 offset 与 lag;
- 断线演练:短暂断开从库网络 30 秒后恢复,看主库日志是 "Partial resynchronization request accepted" 还是 "Unable to partial resync ... Sending full RDB",前者说明 backlog 起效;
- 全量风暴预防:主库故障恢复或网络分区结束后,多个从库同时请求全量会压垮主库,错开从库重连时间,并开启 repl-diskless-sync-delay 攒批发送(见 26 篇)。
主库的从库行值得逐字段读一遍:
redis-cli INFO replication
# slave0:ip=10.0.1.21,port=6379,state=online,offset=88213904,lag=0
# slave1:ip=10.0.1.22,port=6379,state=online,offset=88209765,lag=1
state 有三个典型取值:online 表示已进入命令传播阶段;wait_bgsave 表示正在等主库完成快照;send_bulk 表示 RDB 正在传输。看到 wait_bgsave 或 send_bulk 停留很久,说明卡在 fork 或网络传输上,而不是复制逻辑本身。offset 与主库 master_repl_offset 的差值就是该从库的落后字节数,乘以写入速率可换算成秒数;lag 是从库最后一次向主库上报心跳距今的秒数,它只反映链路存活,不反映数据落后,两个指标不能互相替代。
从库侧则看三个字段:master_link_status:up 表示链路正常,down 时 master_last_io_seconds_ago 会持续增长;master_sync_in_progress:1 表示正在全量同步;slave_repl_offset 是从库已消费到的位置。排障时的顺序应当是"先确认 link 是否 up,再看 sync 是否在进行,最后比较两个 offset",这个顺序能避免在主从彻底断链时还在纠结 offset 差值。
常见坑 / 最佳实践
- 默认 1mb backlog 形同虚设。 写入稍快的实例,几秒断线就触发全量,按公式重新计算并留一倍余量。
- 只调 backlog 不调 output-buffer-limit。 全量同步期间的复制缓冲区先于 backlog 出问题,两者要一起评估。
- 同机房也要容忍分钟级断连。 网络抖动、内核升级、从库 AOF 重写卡顿都会造成数十秒落后,规划时长别按"秒级"算。
- 避开主库高峰挂从库。 新从库上线选择低峰,全量同步的 fork、IO、网络三重压力叠加在线请求是常见的事故来源。
小结
- 复制依赖 offset 游标与 repl_backlog 环形缓冲区,backlog 覆盖范围内断线即可增量续传。
- 全量同步六步:PSYNC、FULLRESYNC、BGSAVE、传 RDB、补发积压、进入传播,成本集中在 fork 与传输。
- backlog 按"写入速率 × 断连时长"规划并共享给所有从库,是性价比最高的稳定性投资。
client-output-buffer-limit replica决定同步期间缓冲区的容错上限,默认值偏保守。- 观察手段以 INFO replication 与主库日志关键字为主,断线演练应成为例行操作。
- 点赞
- 收藏
- 关注作者
评论(0)