上一篇我们把 Redis 的原子能力拆成了 Pipeline、MULTI/EXEC、WATCH、Lua 和 Functions。但单机再快,也可能遇到机器故障、磁盘损坏和流量读扩散。把数据复制到其他 Redis 实例,才能让副本承担读流量、故障接管和数据安全的一部分职责。
"配置一个 replicaof 就有高可用"同样是过度简化。副本第一次连接主节点时要做全量同步;网络短暂抖动后可能只补增量,也可能因为复制积压已经被覆盖而重新传全量。复制默认异步,主节点返回 OK 时副本可能还没处理;WAIT 可以等待副本确认,却不能把 Redis 变成跨节点强一致数据库。
这篇沿着一条副本从连接到追平的完整路径展开:先建立主从复制模型,再拆 replication ID、offset 和 backlog,分别说明全量同步、PSYNC 部分重同步、断线重连、无盘复制与复制延迟,最后讨论读副本、WAIT、持久化和生产排障。
目录
- 主从复制到底解决什么问题
- [复制 ID、offset 与 backlog:副本怎样证明自己落后了多少](#复制 ID、offset 与 backlog:副本怎样证明自己落后了多少)
- 全量同步:副本第一次怎样追上主节点
- PSYNC:断线后为什么有时只补增量
- 复制延迟:收到、处理和可读不是一回事
- 无盘复制、级联复制与读副本
- [WAIT 与数据安全:异步复制能走多远](#WAIT 与数据安全:异步复制能走多远)
- 生产配置与排障清单
一、主从复制到底解决什么问题
Redis 的基础复制是 leader-follower,也就是主节点与副本节点的一对多关系:主节点处理写入,副本接收复制流并尽量重建相同的数据集。

一条写命令的复制路径大致是:
text
客户端写入主节点
↓
主节点执行命令并修改内存
↓
复制流发送给副本
↓
副本解析并执行相同效果的命令
↓
副本确认已处理的 offset
1.1 复制的三个价值
- 读扩展:只读副本可以承接部分查询,降低主节点读压力;
- 故障接管材料:主节点故障时,副本拥有接近最新的数据,可由 Sentinel、Cluster 或人工流程提升;
- 数据安全与隔离:副本可以承担备份、分析或低峰期扫描,减少主节点受影响的概率。
复制不是自动高可用。副本不会自己决定谁是新主,故障检测、选举、切主和客户端重连由 Sentinel、Cluster 或外部编排系统完成;复制本身只负责让数据流动。
1.2 默认是异步复制
主节点通常不会等待副本处理完每条写命令再回复客户端。主节点执行 SET 后就可以返回 OK,复制流在网络和副本事件循环中继续推进:
text
主节点:执行 SET → 返回 OK ─────────────→ 客户端
└──── 复制流 ───→ 副本稍后处理
所以客户端看到成功,并不等于所有副本已经拥有这次修改。网络断开、主机宕机或副本处理缓慢时,最近一段写入可能只存在主节点内存里。
1.3 复制不能代替持久化
如果主节点和所有副本都关闭持久化,主节点错误重启后加载空数据,副本可能跟着同步空数据,把原有副本也清掉。数据重要时,至少要设计:
text
主节点持久化 + 副本持久化 + 跨故障域备份
上一篇的 RDB/AOF 解决"节点重启后恢复到哪里",这一篇的复制解决"另一个节点能否拥有接近的状态"。两者是互补关系,不是二选一。
二、复制 ID、offset 与 backlog:副本怎样证明自己落后了多少
复制不是简单比较"主节点当前有多少 Key"。主节点对外产生一条有顺序的复制流,每个字节都有递增的 replication offset;副本通过 offset 表示自己已经处理到流的哪个位置。
2.1 replication ID 代表一段数据历史
每个主节点都有一个大随机字符串作为 replication ID。相同 replication ID + 相同 offset,意味着双方处于同一段复制历史上的同一位置。
主节点重启后从持久化文件重新作为主节点启动,或者副本被提升为主节点,都会开启新的历史。副本提升后还会保留旧主节点的历史作为 secondary ID,帮助其他副本判断过去的复制流是否仍可衔接。

2.2 复制积压缓冲区保存最近的流
主节点维护 replication backlog,一块环形缓冲区,保存最近发送过的复制流。副本短暂断线后重连,会告诉主节点:
text
我之前跟的是 replication ID = X
我已经处理到 offset = 120000
请从这里继续吗?
只要主节点仍保留同一历史,且 offset 之后的字节还在 backlog 中,就可以做部分重同步。写入速度越快、断线时间越长,落后的区间越容易被环形缓冲覆盖。
常见配置:
conf
repl-backlog-size 1mb
repl-backlog-ttl 3600
repl-backlog-size 不是越大越好:大 backlog 占用内存,但能覆盖更长的断线窗口。规划时按复制流速估算:
text
可覆盖断线时间 ≈ backlog 字节数 / 平均复制流速
还要为流量尖峰留余量,不能拿平时平均写入速率直接下结论。
2.3 offset 相同也不代表业务完全同步
offset 反映复制流处理位置,不等于:
- 副本所在机器已经把数据安全落盘;
- 客户端一定已经从副本读到最新值;
- 外部数据库、消息队列和 Redis 已经完成同一业务事务;
- 应用读副本时不会因为路由或连接池看到旧节点。
它是复制协议的进度坐标,不是全系统一致性坐标。
三、全量同步:副本第一次怎样追上主节点
新副本首次连接、复制历史不匹配,或 backlog 已经覆盖了所需区间时,主节点无法只发送几条增量命令,只能进行 full resynchronization。

典型流程如下:
- 副本连接主节点并完成复制握手;
- 主节点判断无法 partial resync,准备全量同步;
- 主节点 fork 子进程生成 RDB 快照;
- 主节点继续处理客户端写入,并把期间产生的复制流暂存在缓冲区;
- 主节点把 RDB 发送给副本;
- 副本接收、保存/加载 RDB,建立基础数据集;
- 主节点把快照生成期间积累的命令流继续发送给副本;
- 副本执行追赶到最新 offset,进入在线增量复制。
3.1 全量同步不是主节点停机复制
主节点可以继续处理客户端请求,快照期间的新写入不会丢,而是进入后续复制流。代价转移成:fork、COW、RDB 传输、网络带宽和副本加载都在消耗资源。
数据集越大,全量同步持续越久;同步期间写入越密集,主节点保存的增量缓冲越大,COW 内存放大也越明显。多个副本同时首次同步时,Redis 可以复用一次后台保存结果服务多个副本,但网络发送和副本加载仍会产生压力。
3.2 副本加载期间的服务状态
副本在初始同步阶段可能仍保留旧数据,也可能根据配置拒绝读取;加载新 RDB 时通常会有一段阻塞,之后才切换到新的数据集并继续处理复制流。读副本不能简单当作"永远可读且永远最新"的缓存节点,客户端必须理解 master_link_status、复制 offset 和延迟。
3.3 全量同步的资源账单
text
主节点:fork + COW + RDB 生成 + 网络发送
副本:网络接收 + 临时文件/内存 + RDB 加载
链路:RDB 大流量 + 后续命令流
业务:主节点 P99、磁盘队列、内存余量同时变化
如果主节点恰好在高写入、高内存或 AOF 重写期间反复触发全量同步,延迟尖刺会被放大。复制健康的第一目标不是"永远零延迟",而是避免副本频繁掉线又无法 partial resync。
四、PSYNC:断线后为什么有时只补增量
副本与主节点的连接短暂中断后,会自动重连并发送 PSYNC,携带旧 replication ID 和已处理 offset:
text
PSYNC <replication-id> <offset>
主节点检查三件事:
- 这个 replication ID 是否仍然代表已知历史;
- 副本请求的 offset 是否落在 backlog 可提供的范围内;
- 主节点当前状态是否允许沿着这段历史继续发送。
满足条件就返回 CONTINUE,只发送缺失的复制流;否则返回全量同步所需的信息,副本重新接收完整数据集。

4.1 哪些情况会退化为全量同步
- 副本离线太久,缺失区间已被 backlog 环形覆盖;
- 主节点重启或故障切换后,复制历史发生变化;
- 副本保存的 replication ID 与主节点历史无法匹配;
- 复制协议状态、数据文件或网络异常导致无法安全衔接;
- backlog 太小,无法覆盖业务实际断线窗口。
全量同步不是错误,但频繁出现通常说明连接质量、实例规模、backlog 或部署流程存在问题。
4.2 backlog 怎么按业务估算
假设平均复制流速 8 MB/s,目标覆盖 90 秒的网络抖动:
text
最低 backlog ≈ 8 MB/s × 90s = 720 MB
还要加上写入尖峰、协议开销和监控误差。若实际写入在促销时达到平时 5 倍,按平均值配置的 backlog 很可能只覆盖十几秒。
4.3 复制断线不是立即数据丢失
副本断线后,主节点仍可能继续写入;这些写入会保留在 backlog,等待副本重连。如果在 backlog 覆盖范围内,副本最终可以追平,不需要回滚主节点。真正的风险是:主节点在副本尚未追平时故障,而业务又把旧副本直接提升为主节点,此时最近写入可能丢失。
这也是故障切换必须结合复制延迟、持久化和业务 RPO,而不能只看"副本进程还活着"的原因。
五、复制延迟:收到、处理和可读不是一回事
复制延迟不是一个单一数字。至少要区分:
text
主节点生成复制流
↓ 网络传输延迟
副本收到字节
↓ 事件循环排队
副本解析并执行命令
↓ 应用路由 / 读取时机
客户端读到副本数据

5.1 延迟常见来源
- 主节点写入流量过高,复制流持续增长;
- 主副本之间跨地域,RTT、带宽或丢包抖动;
- 副本 CPU 不足,解析和执行追不上;
- 副本执行大范围命令、释放大 Key 或加载数据;
- 磁盘 I/O、AOF fsync、RDB 加载与复制争抢资源;
- 客户端连接池把读请求路由到不同副本;
- 副本本身被用来跑重查询、扫描或备份。
5.2 监控哪些指标
redis
INFO replication
ROLE
关注:
| 指标/字段 | 含义 |
|---|---|
master_link_status |
副本与主节点链路是否在线 |
master_last_io_seconds_ago |
最近收到主节点数据的时间 |
master_repl_offset |
副本已处理的复制位置 |
master_sync_in_progress |
是否正在初始同步 |
| 主副本 offset 差值 | 复制流层面的落后程度 |
repl_backlog_histlen |
backlog 当前有效历史长度 |
sync_full / sync_partial_ok |
全量与部分同步次数 |
应用侧还要记录读副本的路由、读后写一致性需求和副本返回旧值的比例。只看连接状态为 up,无法证明业务已经读到最新数据。
5.3 读副本的读后写问题
用户刚在主节点完成下单,紧接着查询订单详情却被路由到尚未追平的副本,可能看到旧状态。常见解决方式:
- 关键读在写后的一小段时间路由主节点;
- 通过复制 offset 或会话标记选择已追平副本;
- 允许短暂旧读,并在页面/业务状态机中正确表达;
- 对必须强一致的查询不走异步副本。
读扩展换来的是延迟和一致性权衡,不是免费增加主节点能力。
六、无盘复制、级联复制与读副本
6.1 无盘复制减少中间磁盘 I/O
传统全量同步通常是子进程生成 RDB,再通过磁盘文件传给副本。无盘复制让后台子进程直接把 RDB 通过网络发送给副本,减少"先写本地盘、再从本地盘读出"的中间步骤:
conf
repl-diskless-sync yes
repl-diskless-sync-delay 5
repl-diskless-sync-delay 可以让主节点等待片刻,聚合同一批到达的副本请求,避免为相近时间的副本重复生成多个快照。代价是网络成为更直接的瓶颈,失败重试和部署环境支持也要验证。
6.2 级联复制不是复制流的二次改写
副本可以再接受其他副本连接,形成级联结构:
text
主节点 A
├── 副本 B
│ └── 副本 D
└── 副本 C
级联可以减少主节点直接服务的副本数量,适合跨地域或大规模拓扑。但 B 故障会影响 D 的同步路径,D 的延迟也会叠加 B 的处理与网络延迟。拓扑设计要明确每个副本的故障域和提升候选角色。
6.3 副本读流量不能掩盖主节点热点写
读副本适合纯读、可接受短暂旧数据的查询;它不能分担主节点写入、复制流生成和热点 Key 的写竞争。副本数量越多,主节点要维护的网络连接和复制发送工作也越多。
给副本安排大范围 KEYS、无界 SMEMBERS 或分析任务,可能反过来拖慢复制追赶。离线分析应使用独立导出或专门数据管道,而不是把线上副本当无限资源的查询库。
七、WAIT 与数据安全:异步复制能走多远
Redis 提供 WAIT,让客户端等待指定数量的副本确认已经处理到当前复制流位置:
redis
SET order:90001 status paid
WAIT 2 100
它可以缩小"主节点返回后副本尚未处理"的窗口,但有几个边界:
WAIT等的是副本处理确认,不是副本把数据持久化到磁盘;- 超时返回的副本数可能小于目标值,客户端必须决定继续、降级还是失败;
- 它增加写请求延迟,副本越远、越慢,尾延迟越明显;
- 不能把 Redis 的异步复制变成跨节点线性一致提交;
- 主节点和副本同时丢失、故障切换时选错副本等风险仍需备份和业务补偿。
7.1 min-replicas-to-write 是写入保护阀
可以配置主节点在可用副本数量不足或副本延迟过大时拒绝写入:
conf
min-replicas-to-write 1
min-replicas-max-lag 10
它表达的是"没有足够健康副本时宁可停止写",适合对数据安全比可写性更敏感的场景。但网络分区、监控误判和副本维护会直接影响写入可用性,必须配合明确告警、切换和恢复流程。
7.2 复制安全的真实组合
text
异步复制:降低单节点故障风险
+ WAIT / min-replicas:缩小或限制复制窗口
+ RDB/AOF:节点重启后恢复
+ 跨故障域备份:应对整组丢失与误操作
+ 幂等 / 对账 / 补偿:填补最终业务缺口
没有任何单项配置可以独立承诺"数据绝不丢"。先定义 RPO,再决定写延迟、可用性和资源成本。
八、生产配置与排障清单
8.1 一份配置检查起点
conf
replicaof 10.0.0.10 6379
replica-read-only yes
# 按复制流速与目标断线窗口估算
repl-backlog-size 256mb
repl-backlog-ttl 3600
# 大规模初次同步时评估磁盘与网络取舍
repl-diskless-sync yes
repl-diskless-sync-delay 5
# 对数据安全敏感时评估写入保护
min-replicas-to-write 1
min-replicas-max-lag 10
不要直接复制这组数值:backlog、超时、最小副本数和延迟阈值都要按写入速率、网络拓扑、实例大小和 RPO 压测。
8.2 发现延迟时按顺序排查
text
先看 master_link_status 与 sync_in_progress
↓
比较主副本 replication offset 差值
↓
确认是网络慢、主节点生成慢,还是副本执行/加载慢
↓
看 sync_partial_ok 与 sync_full 是否频繁全量
↓
检查 backlog 是否覆盖断线窗口
↓
关联 fork/COW、磁盘、AOF、BigKey 和副本重查询
8.3 线上常见症状与方向
| 症状 | 优先检查 |
|---|---|
| 副本频繁 full sync | 网络断线、backlog 太小、实例重启、复制 ID 变化 |
| offset 持续拉大 | 副本 CPU、慢命令、大 Key、磁盘 I/O、带宽 |
| 主节点 P99 与同步同时尖刺 | fork/COW、全量 RDB、网络发送、AOF 重写 |
| 读副本偶尔旧读 | 路由、读后写、offset 水位、跨地域 RTT |
WAIT 经常超时 |
副本延迟、网络、目标副本数过高 |
| 副本数据被清空 | 主节点空数据重启后同步、错误切主、持久化缺失 |
8.4 上线前的复制演练
- 新副本首次全量同步,记录 RDB 生成、传输和加载时间;
- 人为断开网络,验证在 backlog 覆盖范围内能否 partial resync;
- 让断线超过覆盖窗口,确认告警能识别 full sync;
- 模拟主节点故障,观察副本数据缺口与客户端重连;
- 验证读后写请求不会被错误路由到明显落后的副本;
- 恢复主从拓扑,检查 replication ID、offset、持久化文件和业务对账。
版本提示 :本文以 Redis 7.4 与 Redis 8.0 共享的主从复制、PSYNC、replication ID、backlog、无盘复制和
WAIT机制为基线。Sentinel 的故障检测与选主、Cluster 的槽位路由和自动故障转移将在后续篇目单独展开。
最终可以用一句话概括 Redis 复制:主节点产生带 replication ID 和 offset 的复制流,副本优先用 PSYNC 从 backlog 补缺失区间,无法衔接时再用 RDB/增量流全量追平;复制默认异步,延迟、持久化和故障切换必须一起纳入数据安全设计。
这一篇把"副本怎样追上主节点"展开成了完整协议路径:首次连接走全量同步,短暂断线优先走 PSYNC 部分重同步;replication ID 标记历史,offset 标记位置,backlog 决定断线窗口;无盘复制降低中间磁盘 I/O,读副本扩展查询却带来旧读和资源隔离问题。
带走四句话:① 主节点返回 OK 不等于副本已经处理,Redis 复制默认异步;② backlog 足够大时断线只补增量,不足时就会重新全量同步;③ WAIT 等副本处理确认而不是磁盘落盘,不能替代持久化和备份;④ 复制延迟要同时看网络、主节点生成、副本执行、磁盘和客户端读路由。
下一篇,我们从"复制流已经存在"继续追问"谁来判断主节点真的挂了,以及谁有资格接管"------《Redis 09 · 高可用:Sentinel 如何发现故障并完成切主》。主观下线、客观下线、Leader 选举、从节点晋升和客户端重连,怎样组合成一套可用的故障转移流程?