Redis 复制把同一份数据保存在多个实例中,用于读扩展、故障恢复和高可用。但"主从一致"并不表示每一时刻都完全相同。
Redis 默认采用异步复制:写命令先在主库执行并回复客户端,再通过复制流发送给从库。因此,从库通常会追上主库,但在网络延迟、负载过高或故障发生时,可能暂时落后。
更准确的结论是:Redis 主从复制提供有序的最终一致性,而不是默认提供同步强一致性。
Redis 5.0 起推荐使用 primary/replica 和
REPLICAOF。本文为保持课程标题使用"主库、从库",分别对应 primary、replica。部分命令输出和配置字段仍保留master、slave等历史名称。

一、为什么采用单主写入
1. 写操作只在主库执行
典型复制拓扑中:
- 客户端把写请求发送给主库。
- 主库按顺序执行写命令。
- 主库把产生的数据变更传播给从库。
- 从库按照相同顺序重放复制流。
所有写入先经过同一个主库,可以建立统一的命令顺序,避免多个可写副本同时修改同一 Key 时发生冲突。
2. 主库和从库都可以承担读取
把部分只读流量发送到从库,可以减轻主库压力,但客户端必须接受复制延迟:
text
客户端写入主库成功
↓
命令仍在网络中
↓
立即读取从库,可能得到旧值
如果业务要求"写入后马上读取自己的最新结果",应优先读取主库,或者在切换到从库读取前建立明确的同步屏障。
3. replica-read-only yes 不是强安全边界
默认配置通常为:
conf
replica-read-only yes
它用于避免普通客户端误写从库,但不是完整的安全控制。管理命令仍需通过 ACL、网络隔离和认证保护。
即使把从库改成可写,从库上的本地写入也不会反向同步到主库,而且在重新同步时可能被清空或覆盖。因此,不应把可写从库当成多主复制方案。
二、如何建立复制关系
在准备作为从库的实例上执行:
text
REPLICAOF 172.16.19.3 6379
也可以写入配置:
conf
replicaof 172.16.19.3 6379
Redis 5.0 之前使用 SLAVEOF。现代版本仍可能保留兼容别名,但新配置和文档应使用 REPLICAOF。
取消复制并把实例切换为独立主库:
text
REPLICAOF NO ONE
这个命令只改变复制关系,不会自动完成完整的高可用故障转移流程。生产环境的自动主从切换通常由 Sentinel、Redis Cluster 或托管平台协调。
三、第一次同步为什么需要全量复制

一个新从库没有主库的数据基线,也不知道自己缺少哪些历史命令,所以必须先获得完整数据集。
1. 握手与能力协商
复制连接建立后,从库会进行认证、端口与能力协商,然后发送类似:
text
PSYNC ? -1
?表示还不知道主库的复制 ID。-1表示没有可用于续传的复制偏移量。
主库如果决定执行全量同步,会回复:
text
FULLRESYNC <replication-id> <offset>
从库记录复制 ID 和偏移量,后续断线重连时会使用它们请求部分重同步。
2. 生成并传输数据基线
经典全量同步会生成 RDB 数据基线,并把它发送给从库:
- 主库通过子进程生成 RDB,或者直接把 RDB 流写入复制连接。
- 从库接收完整 RDB。
- 从库丢弃原有数据并加载主库的数据集。
- RDB 生成和传输期间的新写入通过复制命令流补齐。
全量同步使用 RDB,而不是把主库本地 AOF 文件原样发给从库,原因是:
- RDB 是自包含的时间点数据基线,体积通常更紧凑。
- RDB 可以直接通过网络流式生成,不要求主库启用本地 AOF。
- 从库只需要"当前基线 + 后续有序命令",不需要主库的完整本地持久化历史。
- 大 AOF 或多部件 AOF 的加载与重放成本通常更高,也会把本地持久化布局耦合到复制协议。
3. Redis 7 及更早版本的经典缓冲方式
在经典流程中,主库生成和发送 RDB 时仍会继续接收写请求。这些新增命令需要暂存在主库为从库维护的复制输出路径中,等 RDB 传输完成后再发送。
如果全量同步时间很长且写入量很大,主库可能积累大量待发送数据,造成内存压力,甚至触发副本客户端输出缓冲区限制并断开连接。
4. Redis 8+:双通道全量同步
Redis 8 引入双通道全量同步。主库可以并行发送:
- 用于建立数据基线的 RDB 流。
- RDB 生成期间持续产生的复制命令流。
从库先暂存命令流,等待 RDB 成功加载后再按顺序应用增量命令。配置:
conf
replica-full-sync-buffer-limit 0
用于限制从库在全量同步期间可积累的命令流。值为 0 时,会继承副本客户端输出缓冲区的硬限制。达到上限后,更多数据可能转而在主库侧积累。
这一机制需要主从双方完成能力协商;旧版本节点仍使用经典流程。它把部分全量同步缓冲压力从主库转移到正在同步的从库,但并没有消除内存容量规划:主库和从库都需要为高写入期间的复制流预留空间。
5. 从库加载基线时不一定持续可读
获得 RDB 并不等于从库已经可以立即提供最新读取。经典全量同步需要丢弃或替换旧数据集并加载新 RDB,加载期间可能阻塞请求或按配置返回错误。
replica-serve-stale-data 只决定复制链路断开或同步未完成时是否允许返回旧数据,不会让正在替换数据集的所有阶段都天然无阻塞。使用 repl-diskless-load swapdb 等模式可以先在临时数据集加载后再交换,缩短不可用窗口,但会增加峰值内存,并需要验证模块与版本兼容性。
因此,把读流量放到从库时,客户端仍要处理全量同步期间的旧读、错误和连接重试。
四、磁盘全量同步与无盘复制
主库发送 RDB 有两种常见方式。
1. 基于磁盘
子进程先把 RDB 写入磁盘,完成后由主进程读取文件并发送给从库。
优点是生成的 RDB 可以被多个后续步骤复用,故障分析也较直接;代价是需要磁盘空间,并产生写盘和再次读盘的 I/O。
2. 无盘复制
现代配置常见默认值:
conf
repl-diskless-sync yes
repl-diskless-sync-delay 5
子进程把 RDB 直接写入从库连接,不先保存到主库磁盘。它适合网络较快而主库磁盘较慢的环境。
repl-diskless-sync-delay 让主库等待一小段时间,以便把同时到来的多个从库合并到同一轮 RDB 生成和传输中。因此,"多个从库连接"不一定意味着"每个从库都单独 fork 一次"。
无盘复制失败后通常需要重新开始一轮全量同步。是否让从库直接从 socket 加载 RDB,则由 repl-diskless-load 等配置决定,还需要考虑旧数据保留、峰值内存和模块兼容性。
五、正常运行时如何持续同步
完成全量同步后,主从之间保留长连接。主库把新的数据变更编码成复制命令流,持续发送给从库。
复制流具有两个关键属性:
- 有序:从库按主库生成的顺序执行命令。
- 异步:主库默认不等待从库确认,就可以向客户端返回写入结果。
从库会周期性向主库发送 ACK,报告自己已经处理到的复制偏移量。主库通过这些 ACK 判断从库延迟,也为 WAIT、健康检查和故障转移提供依据。
复制连接正常并不表示从库没有延迟。如果主库写入速度超过网络或从库处理能力,偏移量差距仍会不断扩大。
六、断线后如何进行部分重同步

从 Redis 2.8 开始,短暂断线后不一定要重新传输全部数据。Redis 使用三项信息判断能否续传:
- 主库的复制 ID。
- 从库最后处理到的复制偏移量。
- 主库复制积压缓冲区中仍然保留的历史范围。
1. 复制 ID
复制 ID 标识某一条数据历史。它比旧文章中常用的实例 runID 更准确,因为复制协议关心的是数据历史,而不只是进程身份。
主库发生角色变化时会生成新的复制 ID,同时保留第二复制 ID 和切换偏移量,用于让原拓扑中的从库在故障转移后仍有机会执行部分重同步。
2. 复制偏移量
主库每向复制流追加字节,master_repl_offset 就向前推进。从库处理复制流后,也维护自己对应的偏移量。
偏移量表示字节流位置,不是"执行了多少条命令"。两个偏移量的差值可以反映尚未复制或尚未处理的数据量。
3. 复制积压缓冲区
repl-backlog-size 控制主库保留多长一段最近复制历史。它是一个全局环形历史缓冲区,可以被多个从库用于部分重同步,并不是每个从库各自拥有一份完整 backlog。
从库重连时发送:
text
PSYNC <replication-id> <offset>
如果复制 ID 匹配,并且请求的下一字节仍在 backlog 中,主库回复 CONTINUE,只发送缺失部分。
如果历史已经被环形缓冲区覆盖,或复制 ID 无法衔接,主库回复 FULLRESYNC,重新执行全量同步。
七、三类复制缓冲区不要混淆
1. 复制积压缓冲区
由 repl-backlog-size 控制,保存最近一段全局复制历史,主要用于断线后的部分重同步。
2. 从库客户端输出缓冲区
每个已连接从库都有待发送数据。相关限制示例:
conf
client-output-buffer-limit replica 256mb 64mb 60
如果某个从库长期消费过慢,输出数据超过硬限制,或者超过软限制并持续指定时间,主库会断开该从库,避免它无限消耗内存。
3. Redis 8 全量同步暂存区
由 replica-full-sync-buffer-limit 控制,位于正在全量同步的从库侧,用来暂存与 RDB 并行到达的复制命令流。
三者分别解决历史续传、在线发送积压和全量同步并行流问题,不能把它们统称为同一个 replication buffer。
八、怎样估算 repl-backlog-size
原文使用"主库写入速度减去网络速度"的简单公式,容易低估需求。backlog 保存的是一段按字节编号的复制历史,核心问题是:需要覆盖多长的断线与重连窗口。
更实用的估算方式是:
text
backlog 大小
≈ 峰值复制字节速率
× 希望容忍的最长断线/重连时间
× 安全系数
例如,峰值复制流为 8 MB/s,希望从库断线 30 秒后仍能部分重同步,安全系数取 2:
text
8 MB/s × 30 s × 2 = 480 MB
还需要考虑:
- 大 Value 会使复制字节速率突然升高。
- 全量同步和从库加载可能延长真正的追赶时间。
- 网络抖动、故障检测和 DNS/连接重建都占用窗口。
- backlog 只能避免历史丢失,不能解决从库在线消费速度长期低于主库写入速度的问题。
应结合 repl_backlog_histlen、实际偏移量差、断线时长和全量同步次数持续调整,而不是只按平均 QPS 估算。
九、级联复制能解决什么问题
Redis 允许从库继续作为其他从库的上游:
text
主库
├─ 从库 A
│ ├─ 从库 A1
│ └─ 从库 A2
└─ 从库 B
级联复制可以减少主库直接维护的连接数和全量同步网络压力,适合跨区域分发或大量只读副本。
但它也增加了代价:
- 下游从库的延迟包含每一级传播延迟。
- 中间从库故障会影响整条下游链路。
- 拓扑、监控和故障转移更复杂。
- 中间节点同样需要承担全量同步、缓冲和网络资源。
所以,级联复制是资源和拓扑的取舍,不是副本越多越应该默认使用。
十、为什么读写分离仍可能读到旧数据
1. 正常复制延迟
写命令已经在主库执行,但尚未到达从库。客户端立即读从库时会看到旧值。
2. 从库处理变慢
从库正在加载 RDB、CPU 不足、执行大命令,或者网络接收速度不足,都可能让复制偏移量落后。
3. 网络分区
主库仍对外写入,从库与主库断开。只读流量如果继续访问从库,会读到断线前的状态。
4. 故障转移丢失未复制写入
主库回复客户端后,如果命令尚未到达将被提升的新主库,原主库立刻故障,那么已确认写入仍可能丢失。
这不是复制错误,而是异步复制的固有一致性边界。
十一、如何缩小一致性窗口
1. 对关键读执行主库读取
账户余额、订单状态和刚写入后的确认查询,不应无条件发送到任意从库。可以使用主库读、会话粘滞或业务版本号控制读取位置。
2. 使用 WAIT
text
SET order:1001 paid
WAIT 1 2000
WAIT 1 2000 表示等待当前连接此前的写入,被至少一个从库确认处理,最多等待 2000 毫秒。
客户端必须检查返回的确认数量。超时不代表写入自动回滚,主库上的命令可能已经成功。
WAIT 能提高真实数据安全性,但不能让 Redis 变成严格强一致系统:Sentinel 或 Cluster 不一定选择已经确认该写入的从库,网络分区和多个故障仍可能造成数据丢失。
3. 需要落盘确认时使用 WAITAOF
Redis 7.2+ 的 WAITAOF 可以等待本地和指定数量从库把当前连接此前的写入同步到各自 AOF。它比 WAIT 多了一层落盘确认,但要求相应实例启用 AOF,并且仍不能替代一致的故障转移策略。
4. 限制落后副本不足时的写入
conf
min-replicas-to-write 1
min-replicas-max-lag 10
这表示主库只有在至少一个从库的延迟不超过 10 秒时才接受写入。默认 min-replicas-to-write 为 0,即关闭该保护。
这个配置根据从库周期性 ACK 判断健康,不能确认每一条写命令都已经到达指定数量从库。它只能限制风险窗口,不能提供同步复制保证。
十二、怎样减少不必要的全量同步
1. 合理扩大 backlog
让 backlog 能覆盖常见的网络闪断、进程短暂停顿和重连时间。
2. 避免从库长期跟不上
如果从库的 CPU、网络或内存明显弱于主库,它即使不断重连也可能持续掉队。需要扩容从库或减少读取、模块和后台任务压力。
3. 控制单实例数据规模
数据集越大,RDB 生成、传输和加载越慢,全量同步期间的写入积压越多。达到单实例资源边界时,应考虑分片,而不是无限增大缓冲区。
4. 批量接入从库
利用 repl-diskless-sync-delay 聚合同一时间接入的从库,使它们共享一轮 RDB 生成和流式传输。
5. 避免频繁重启和拓扑抖动
容器健康检查过于激进、网络设备空闲超时、主机资源抖动和错误的自动化切换,都可能把短暂问题放大成重复全量同步。
十三、复制本身不能代替持久化
如果主库关闭持久化并设置自动重启,存在一个危险流程:
- 主库进程崩溃并丢失内存数据。
- 进程自动重启,启动为空数据库。
- 从库重新连接并认为空主库是新的复制源。
- 从库数据被同步为空。
所以,主从复制不能代替 AOF、RDB 和备份。若主库确实不启用持久化,应谨慎设计自动重启与故障转移,避免空实例重新成为权威数据源。
复制解决副本冗余,持久化解决进程与机器重启后的数据恢复,备份解决误操作和整个故障域损坏。三者作用不同。
十四、如何检查复制状态
1. INFO replication
主库重点观察:
role、connected_slaves:角色和已连接从库数。master_replid、master_replid2:当前与第二复制 ID。master_repl_offset:主库复制流偏移量。repl_backlog_active、repl_backlog_size:backlog 状态与容量。repl_backlog_first_byte_offset、repl_backlog_histlen:当前可续传的历史范围。- 每个
slaveN条目的state、offset、lag。
从库重点观察:
master_link_status:与上游连接是否正常。master_last_io_seconds_ago:距上次与主库通信的时间。master_sync_in_progress:是否正在全量同步。slave_repl_offset:从库处理到的复制偏移量。master_link_down_since_seconds:连接断开持续时间。
历史字段名仍可能使用 master、slave,应以当前版本 INFO 实际输出为准。
2. ROLE
ROLE 可以快速查看实例角色、上游状态、复制偏移量和下游从库列表,适合脚本化检查。
3. 监控全量同步和缓冲区
需要持续观察:
- 全量同步次数是否突然增加。
- backlog 历史能覆盖多少秒峰值写入。
- 从库输出缓冲区是否增长。
- 主从偏移量差与
lag是否持续扩大。 - RDB 生成、网络传输和从库加载耗时。
fork、COW、内存峰值和磁盘/网络吞吐。
十五、主从同步检查清单与答案
1. 主库写入成功是否表示所有从库已经收到?
**不是。**Redis 默认异步复制,主库通常不会等待从库确认后才回复客户端。
2. 从库可读是否表示一定能读到最新值?
**不是。**从库可能存在正常复制延迟、网络分区或处理积压。
3. 第一次同步为什么不直接传输主库 AOF?
**因为复制需要的是完整数据基线和后续有序命令流。**RDB 更紧凑、加载更快、可以无盘流式传输,也不依赖主库是否启用 AOF。
4. backlog 越大是否就能解决所有复制延迟?
**不能。**backlog 只能延长断线后可部分重同步的历史窗口。如果从库长期消费速度低于主库写入速度,仍会持续落后或被输出缓冲区限制断开。
5. repl-backlog-size 是否应该按命令条数计算?
**不应该。**复制偏移量按字节推进,应使用峰值复制字节速率、希望覆盖的断线时间和安全系数估算。
6. Redis 8 全量同步是否不再需要缓冲新命令?
**仍然需要。**只是主库可以让 RDB 与命令流并行发送,由从库先暂存增量;达到从库限制后,压力仍可能回到主库侧。
7. WAIT 是否提供强一致事务?
**不提供。**它等待复制 ACK,不会回滚超时写入,也不能保证故障转移一定选择已确认该写入的从库。
8. min-replicas-to-write 是否表示每次写入都同步复制到 N 个从库?
**不是。**它根据最近 ACK 和允许延迟决定是否接收写入,只是降低风险窗口。
9. 从库开启只读是否就可以暴露到公网?
**不可以。**只读只防止普通误写,不会关闭管理命令。必须使用网络隔离、ACL、TLS 和最小权限。
10. 复制是否可以取代 AOF、RDB 与备份?
**不能。**错误写入、空主库重启和故障域损坏都可能被复制到所有从库。
十六、核心结论
Redis 主从复制可以概括为三条路径:
- 全量同步:建立完整 RDB 数据基线,再追上同步期间的命令流。
- 命令传播:通过长连接持续发送有序写命令。
- 部分重同步:依靠复制 ID、偏移量和 backlog 补发断线期间缺失的字节流。
主从库最终能收敛到同一状态,依赖的是单主写入、有序复制流和断线续传。但它默认仍是异步系统。真正的生产设计还需要同时考虑读一致性、全量同步加载窗口、backlog 容量、从库输出缓冲区、Redis 8 双通道同步缓冲、WAIT/WAITAOF、故障转移选择、持久化与异机备份。