06丨Redis 数据同步:主从库如何实现数据一致?

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 数据基线,并把它发送给从库:

  1. 主库通过子进程生成 RDB,或者直接把 RDB 流写入复制连接。
  2. 从库接收完整 RDB。
  3. 从库丢弃原有数据并加载主库的数据集。
  4. 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 等配置决定,还需要考虑旧数据保留、峰值内存和模块兼容性。

五、正常运行时如何持续同步

完成全量同步后,主从之间保留长连接。主库把新的数据变更编码成复制命令流,持续发送给从库。

复制流具有两个关键属性:

  1. 有序:从库按主库生成的顺序执行命令。
  2. 异步:主库默认不等待从库确认,就可以向客户端返回写入结果。

从库会周期性向主库发送 ACK,报告自己已经处理到的复制偏移量。主库通过这些 ACK 判断从库延迟,也为 WAIT、健康检查和故障转移提供依据。

复制连接正常并不表示从库没有延迟。如果主库写入速度超过网络或从库处理能力,偏移量差距仍会不断扩大。

六、断线后如何进行部分重同步

从 Redis 2.8 开始,短暂断线后不一定要重新传输全部数据。Redis 使用三项信息判断能否续传:

  1. 主库的复制 ID。
  2. 从库最后处理到的复制偏移量。
  3. 主库复制积压缓冲区中仍然保留的历史范围。

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. 避免频繁重启和拓扑抖动

容器健康检查过于激进、网络设备空闲超时、主机资源抖动和错误的自动化切换,都可能把短暂问题放大成重复全量同步。

十三、复制本身不能代替持久化

如果主库关闭持久化并设置自动重启,存在一个危险流程:

  1. 主库进程崩溃并丢失内存数据。
  2. 进程自动重启,启动为空数据库。
  3. 从库重新连接并认为空主库是新的复制源。
  4. 从库数据被同步为空。

所以,主从复制不能代替 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 主从复制可以概括为三条路径:

  1. 全量同步:建立完整 RDB 数据基线,再追上同步期间的命令流。
  2. 命令传播:通过长连接持续发送有序写命令。
  3. 部分重同步:依靠复制 ID、偏移量和 backlog 补发断线期间缺失的字节流。

主从库最终能收敛到同一状态,依赖的是单主写入、有序复制流和断线续传。但它默认仍是异步系统。真正的生产设计还需要同时考虑读一致性、全量同步加载窗口、backlog 容量、从库输出缓冲区、Redis 8 双通道同步缓冲、WAIT/WAITAOF、故障转移选择、持久化与异机备份。

相关推荐
前端 贾公子1 小时前
LangGraph == 图的状态(State)管理 (上)
java·开发语言·数据库
薛晓刚1 小时前
PGA 超限的一次应急处置:扩容、清游标、杀会话
数据库
Zooproxy全球IP代理1 小时前
海外代理IP纯净度评估:指标、检测与调度实践
网络协议·tcp/ip·php
CJi0NG1 小时前
【自用】MySQL-事务
数据库·mysql
Solis程序员2 小时前
Stripe 幂等表:把「不确定的重试」变成「查表返回」
前端·安全·bootstrap·php·agent
我不会插花弄玉2 小时前
4.string类型【由浅入深-redis】
数据库·redis·缓存
PHP实战开发录2 小时前
MySQL数字排序为什么乱
数据库·mysql·php
不停喝水3 小时前
【前端转全栈java速通课】 项目实战④7-11节 操作数据库-登录-注册-修改密码-注销-mubatis-plus简化crud-
java·前端·数据库