2026/10/23 3:59:09

Redis主从复制深度解析:全量同步与增量同步机制拆解

Redis主从复制深度解析:全量同步与增量同步机制拆解 说到 Redis 的高可用架构主从模式是所有人绕不开的起点。但实际搞过的人都知道主从复制远不止“读写分离”这么简单——真正决定系统稳不稳的是它内部那套数据同步机制。这篇文章我会把全量数据同步和增量数据同步两条主线彻底拆开讲透包括 RDB 快照生成、复制积压缓冲区、复制偏移量这些核心概念也会穿插配置参数、监控命令和我在生产环境里踩过的坑。适合两类人读一类是刚接触 Redis、正准备搭主从架构的开发者另一类是已经在用主从但遇到同步问题不知道怎么排查的运维和 DBA。1. Redis 主从模式先搞清楚它解决什么问题1.1 单节点 Redis 的三大痛点先说实际中的问题。单机部署 Redis不管你的业务量多小迟早会遇到三个坎。第一个是读写吞吐瓶颈。Redis 本身是单线程模型虽然处理速度极快但一台机器的 CPU、内存、网络带宽都是有上限的。当你的应用层出现大量 Redis 操作——比如缓存穿透防护、热点数据查询、秒杀场景的扣减库存——单个实例能扛住的 QPS 就有限。就算你把 Redis 优化得很极致单机计算资源的天花板摆在那里压力一大就得另想办法。第二个是数据安全风险。Redis 的高性能建立在纯内存基础上如果机器故障、断电内存里几十 GB 的数据瞬间就没了。有人会说我有持久化啊RDB 快照加 AOF 日志。没错持久化能解决“进程重启”后的数据恢复但解决不了“整台机器挂了”的问题。机器宕机需要换机器、重新部署期间数据服务是断着的。更现实的是如果你的 RDB 文件跟实例在同一块磁盘上磁盘坏了连恢复的底子都没有。第三个是故障恢复的自动化程度。单节点挂了能不能自动把流量切走答案是不能。你只能靠监控报警然后人工介入或者提前做一些脚本化的重启和切换操作。但脚本切流量有一个前提得有一台机器存着另一份可用的数据。这三个痛点叠加在一起主从模式就是最直接、成本最低的解法。主节点负责写从节点拷贝主节点的数据读流量可以分摊到从节点。主节点挂掉从节点上的数据还在配合哨兵这类组件就能快速提升新的主节点。这也是为什么我一直说主从模式是所有 Redis 高可用方案的基石。1.2 主从模式的核心思路与能力边界主从模式的思路很朴素一台主节点master负责接收写命令一台或多台从节点replica通过复制机制与主节点保持数据一致。应用层的读请求打到从节点写请求仍走主节点。这个拓扑的好处有三点读写分离横向扩展读能力。数据冗余多副本存储。为哨兵模式的自动故障转移打基础。但这里必须把主从模式的能力边界说清楚因为很多人的理解有偏差。主从模式本身不提供自动故障转移。主节点挂掉之后你手动执行REPLICAOF NO ONE可以把某个从节点提升为主节点但谁来检测故障、谁来决策提升、谁来通知客户端切换连接地址主从自己解决不了。所以生产环境的常见做法是主从之上再叠一层哨兵 Sentinel由哨兵负责监控、通知和自动故障转移。主从模式的数据一致性是“最终一致”不是强一致。主节点写入成功不意味着从节点马上就能读到。网络延迟、从节点自身负载都会导致主从之间存在短暂的数据窗口。如果业务要求严格的读一致性那多数从节点的读都可能读到旧数据这时候就要考虑其他手段比如读主节点、引入分布式锁或者使用更严格的一致性方案。还有一个容易被忽视的点主从模式下从节点默认是只读的。这个设计是故意的防止意外写入导致主从数据不一致。如果你发现某台从节点上的数据和主节点不一样先别急着重启去查一下是不是有人登录上去手动 SET 过或者某个应用误连到了从节点的端口写数据。从节点本质上是独立运行的 Redis 实例也拥有自己的持久化能力。所以有句话常说从节点可以关闭 AOF、分担主节点的持久化压力。但这只在主从复制正常的前提下成立。把从节点当成纯缓冲、不开启持久化一旦从节点重启它就得重新从主节点拉全量数据这个过程本身会产生很大的成本和风险。这一点在后面章节会展开。2. 全量数据同步第一次连接时到底发生了什么2.1 触发条件与前置握手全量同步可以理解成一个 Redis 实例在加入主从体系、或者复制中断无法续传时进行的一次“整库拷贝”。第一次建立主从关系时从节点会向主节点发送一条PSYNC命令格式大概是psync ? -1意思是我不知道你的复制 ID也不知道复制偏移量咱们从头开始。主节点收到之后如果判断确实无法做增量同步就返回FULLRESYNC replid offset告诉从节点准备接收全量数据同时把复制 ID 和当前复制偏移量这两个关键信息告诉它。这里有几个点值得展开。第一为什么从节点要说? -1因为它是第一次见主节点不知道主节点是谁也不知道主节点处理到了哪条命令。用?表示复制 ID 未知用-1表示偏移量未知等于显式告诉主节点我没法给你提供增量起点你直接把全部数据给我吧。第二主节点返回的replid是主节点独有的复制 ID。每次主节点生成 RDB、开始全量同步都会用自己的 replid 标识这次复制的身份。从节点会把这个 replid 存下来之后的增量同步都基于它。如果主节点重启并重新生成了 replid注意重启后可能从 RDB 文件中恢复旧的 replid但如果没有持久化可用就会生成新的所有从节点就会发现旧 replid 对不上不得不重新做全量。第三从 2.8 版本开始PSYNC替代了老的SYNC命令核心改进就是支持断线续传。所以现在的 Redis 版本主从第一次同步或者复制中断后无法续传时才会走全量同步中断后还能续传的情况就走增量同步也叫部分同步 partial resync。这两个模式的关键分界点就在复制积压缓冲区里是否还找得到从节点的偏移量具体细节第三章详述。2.2 RDB 生成与传输的两种路径主节点在返回FULLRESYNC之后紧接着要做的事就是生成一份当前数据集的内存快照也就是 RDB 文件。生成快照有两种方式基于磁盘的传统方式以及无盘复制方式。传统方式下主节点执行BGSAVEfork 一个子进程出来由子进程把内存数据写成 RDB 文件父进程继续对外提供服务。所以 BGSAVE 期间主节点不会被阻塞这是 Redis 的单线程模型之下做得很漂亮的一点。但这也带来了一个复杂性子进程生成快照的同时主进程依然在接收新的写命令。那这些新命令怎么办这就得靠复制积压缓冲区repl backlog来兜底。RDB 文件写完之后主节点把文件通过主从之间的连接发给从节点。从节点收到 RDB 文件之后如果它自己开启了 AOF会先把当前的 AOF 文件重写清理一下然后清空自身现有数据再把 RDB 文件加载进内存。注意从节点加载 RDB 期间它的主循环是阻塞的无法对外提供读取服务。节点数据量越大阻塞时间就越长。这个现象在生产中非常常见——当主节点内存有几 GB 甚至几十 GB 的时候第一次同步新从节点从节点会有一段时间完全读不了数据。无盘复制是另一个选项对应配置项是repl-diskless-sync。在这种模式下主节点不生成 RDB 文件落到磁盘而是直接在内存里生成一份 RDB 格式的数据快照通过 socket 把这份数据流式地发送给从节点。无盘复制的好处是减少磁盘 IO避免在磁盘空间紧张或者 IO 负载高的时候因为写 RDB 文件把主节点拖垮。它的限制是在生成和传输快照期间主节点要持有这份数据快照对应的内存拷贝整体内存和网络占用更大。我在实践中的倾向是这样的如果主节点的磁盘 IO 本身就很紧张或者磁盘剩余空间不够内存的 1.5 倍RDB 生成要临时占用空间无盘复制会是救命选项否则传统有盘方式在大多数场景下更省内存、更可控。无盘复制还有一个配套参数repl-diskless-sync-delay让主节点等一等看是否有多个从节点同时来做全量同步。如果有就把多个同步合并成一个避免重复生成多份快照降低瞬时负载。2.3 全量同步期间的命令兜底这是全量同步里最容易被忽略的环节但恰恰最能体现 Redis 复制的设计细节。主节点 fork 出子进程生成 RDB这个过程可能持续好几秒甚至几十秒。期间主节点并没有闲着依然在接收客户端的写命令。如果这些写命令只写入内存、没有额外记录那从节点加载完 RDB 之后就会缺掉这几十秒内的数据主从立刻就不一致了。为此Redis 引入了复制积压缓冲区。主节点在执行写命令时除了正常更新内存还会把这条命令按 Redis 内部协议格式追加到积压缓冲区。积压缓冲区本质上是环形队列容量由repl-backlog-size控制默认 1MB。全量同步进行中、从节点还没加载完 RDB 时主节点会把从 BGSAVE 开始到 RDB 传输完成期间的新写命令全都放进积压缓冲区。等到从节点加载完 RDB主节点再把缓冲区里的部分命令补发给它。所以一个完整的全量同步流程实际上包含三个阶段主节点生成 RDB 并传输给从节点。在此期间主节点把新写命令持续记录到积压缓冲区。RDB 加载完成后主节点把积压缓冲区里的增量命令发给从节点让从节点追平到最新的数据状态。这里有一个关键点必须强调积压缓冲区的大小决定了全量同步期间最多能兜住多少新命令。如果全量同步耗时特别长而repl-backlog-size设置得很小导致缓冲区被新命令写满、旧命令被覆盖那么即使从节点完成了 RDB 加载它依然缺少一部分命令。主节点会发现无法按照偏移量增量续传于是只能再次发起全量同步形成复制恶性循环。我见过一个真实场景某业务主节点写 QPS 很高默认 1MB 的 backlog 在全量同步的 10 多秒内就被写爆新从节点反复全量同步整个集群的 CPU 和网络被打满。后面把repl-backlog-size调大、磁盘换成 SSD才算彻底解决。3. 增量数据同步断线重连后的精准续传3.1 复制积压缓冲区的环形设计增量同步是 Redis 主从复制里更常见也更“省钱”的一种同步方式。它解决的问题是从节点断线一段时间后重新连接主从之间不需要从头再来一份全量数据只需把从节点断线期间缺失的那部分命令补上。增量同步的核心基础就是复制积压缓冲区。这个缓冲区在主节点上是一块环形存储区域新命令不断从尾部写入头部旧命令因为容量限制逐渐被覆盖。Redis 对缓冲区的读写是字节级别的但它记录的并不是单纯命令文本而是以 Redis 内部协议格式存在的写命令流。环形缓冲区的最大特点是“只追新不保旧”。如果从节点断线时间太长它断线时的偏移量已经被新命令冲刷出缓冲区主节点就没办法通过增量找回缺失的命令只能退回到全量同步。所以repl-backlog-size的设置本质上是在做一道经济学题你愿意用多少内存去换取从节点断线后能通过增量方式恢复的窗口时间。这个窗口可以用简单公式估算窗口时间 ≈ repl-backlog-size / 主节点每秒实际写入的命令字节数举个例子主节点每秒写入 50KB 的命令数据backlog 配置默认 1MB那窗口大概只有 20 秒。如果从节点经常因为网络抖动断线 30 秒以上那 1MB 的默认值完全不够用至少要 8MB 甚至更高。3.2 复制偏移量的推进逻辑前面一直在说“偏移量”这里把它彻底讲透。每个参与复制的节点不管是主还是从都会维护一个复制偏移量 offset。主节点每向从节点发送 N 字节的命令数据自己的 offset 就增加 N从节点每收到 N 字节命令并成功应用后offset 也增加 N。所以正常工作的主从两者的 offset 最终会趋近相等。但偏移量的推进在主从两端不是完全同步的。主节点发送命令到从节点应用命令中间存在网络传输和排队处理的时间差。所以在某一瞬间去比较主从 offset大概率能看到差值这个差值就是主从延迟的量化体现。用INFO REPLICATION命令可以查看从节点状态lag字段表示从节点最后一次与主节点心跳通信的延迟秒数。有了 offset主节点就能在从节点发送PSYNC时精确定位增量起点。从节点断线前记住了自己应用到了哪个 offset重连之后随PSYNC命令发给主节点。主节点检查这个 offset 是否还在积压缓冲区里存在就说明缺失命令都在可以增量续传不存在就说明断线太久某些命令已被覆盖只能全量重来。机制看起来简单但实际落地时有个容易出问题的细节offset 的判断不能只看“起点还在缓冲区里”还要校验复制身份是否一致也就是 replid 对不对。这能防止一种情况集群发生主从切换原本的从节点被提升为主节点它的 replid 变化了旧的从节点还拿着旧 replid。如果仅比对 offset可能导致错误地把 A 节点的数据增量同步到 B 节点上造成数据错乱。Redis 用 replid 标识每一条复制流身份对不上宁可全量也绝不错接。3.3 部分重同步与全量同步的判定细节从 2.8 版本开始PSYNC支持两种结果CONTINUE表示可以走增量同步FULLRESYNC表示要走全量同步。从节点发送psync replid offset之后主节点的判定逻辑大致如下如果从节点发来的 replid 等于主节点当前 replid并且 offset 落在积压缓冲区有效范围内返回CONTINUE从 offset 之后逐命令补发。如果 replid 不匹配大概率是因为主从切换后旧副本还在用旧 ID返回FULLRESYNC。如果 offset 已经超出积压缓冲区的最小边界也就是缺失长度大于 backlog 容量返回FULLRESYNC。如果从节点发来的是psync ? -1无条件走全量。还有一种场景很容易让人困惑主从切换之后新的主节点怎么处理旧从节点的PSYNC这里涉及 replid2 字段的设计。每个 Redis 节点会保存两个复制 ID当前正在使用的 replid以及上一个 replid2。当节点从从变主、或者作为从节点同步了新的复制流时旧的 replid 会保留到 replid2 中。这样一来那些仍持有旧 replid 的从节点来同步时新主节点能通过 replid2 认出它们只要 offset 还在积压缓冲区里依然可以做增量续传不需要全量。这个设计相当精巧能大幅降低主从切换后的复制成本。我实际维护的某套集群里主从切换完成后从节点很快就能通过 replid2 加 offset 的增量方式追上新主节点数据几秒钟内 IO 恢复正常。相比没有这个机制的老版本切换后的全量同步压力被从根源上避免了。4. 关键参数与配置实操4.1 主从配置必知的几个参数主从模式的搭建本身很简单从节点 redis.conf 里加一行replicaof masterip masterport或者用命令REPLICAOF masterip masterport都能建立关系。但生产环境里光建立关系不够有些关键参数设不对后续会有很多麻烦。下面是我常用的配置清单以及每个参数的用途。参数默认值建议与说明replicaof无指定主节点地址从节点启动后会自动开始复制repl-backlog-size1mb复制积压缓冲区大小建议按断线窗口和写 QPS 测算一般不要低于 32mbrepl-backlog-ttl3600积压缓冲区在主节点没有从节点连接时保留多久超时后释放内存释放后再有从节点来连只能全量同步repl-diskless-syncno是否启用无盘复制IO 紧张的场景可以打开repl-diskless-sync-delay5无盘复制的延迟等待时间用于合并多个从节点的全量同步min-replicas-to-write0主节点至少要有多少个健康从节点时才接受写命令0 表示关闭限制min-replicas-max-lag10从节点最大延迟秒数超过该值认为从节点不健康与 min-replicas-to-write 配合使用replica-read-onlyyes从节点只读生产环境不要改成 no其中min-replicas-to-write和min-replicas-max-lag是一对组合拳语义是主节点写入前先检查在线且延迟不超过阈值的从节点数量不够就拒绝写服务。这本质是一个“副本安全”开关避免主节点在没有从节点同步的情况下继续接收写请求、数据随后因主节点故障而丢失。这个开关在账户、订单等对数据安全要求高的场景里很有用但在普通读多写少场景保持默认 0 即可——如果只有一台主节点、没有从节点开了这个配置会导致所有写都被拒。还有两个容易被忽视但同样重要的配置masterauth主节点开启密码时从节点要配置 masterauth 才能完成认证。repl-timeout复制超时时间默认 60 秒。如果主从之间网络延迟大或者跨机房建议适当调大否则会因为心跳超时被误判为断开连接频繁触发重连甚至全量同步。4.2 如何快速查看主从运行状态状态查看是持有 Redis 的人都要掌握的基本功。很多看似玄乎的问题一条INFO命令就能看到端倪。登录从节点执行INFO REPLICATION会看到类似输出role:slave master_host:192.168.1.10 master_port:6379 master_link_status:up master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_read_repl_offset:123456789 slave_repl_offset:123456789 slave_priority:100 slave_read_only:1 connected_slaves:0重点看几个字段role确认自己是从节点。master_link_statusup表示与主节点连接正常down表示断开。长时间down立刻查网络和 repl-timeout 配置。master_sync_in_progress1 表示正在做全量同步长期不为 0 要警惕同步卡住。master_last_io_seconds_ago上一次和主节点通信距现在多少秒持续增大说明主从可能断连或主节点负载太高。slave_repl_offset从节点当前的复制偏移量。在主节点上执行INFO REPLICATION重点看connected_slaves和每个从节点的lagconnected_slaves:2 slave0:ip192.168.1.20,port6379,stateonline,offset123456789,lag0 slave1:ip192.168.1.30,port6379,stateonline,offset123456789,lag2lag是从节点延迟秒数如果持续超过 10 秒就要看是网络问题还是从节点负载太高。另外主节点的INFO REPLICATION里如果出现stateoffline说明这个从节点已经失联需要检查从节点日志或网络链路。用redis-cli --stat也能看实时状态但INFO REPLICATION是排查主从问题最简单有效的手段建议优先掌握。5. 实战中经常踩的坑与排查实录5.1 全量同步频繁触发整个集群被拖垮这是我在维护一组 Redis 主从时遇到的最典型故障也是社区里被问得最多的问题。现象主从连接正常但从节点每隔几分钟就断线重连每次重连都触发全量同步主节点 CPU 飙升、带宽打满、QPS 抖动。排查步骤第一步登录主节点执行INFO REPLICATION观察从节点的 state 和 offset 变化。如果 offset 经常回退说明从节点确实在反复全量同步。第二步查看主节点慢日志和内存。如果主节点同时在做 BGSAVE 生成 RDBCPU 会有明显峰值这是全量同步的典型特征。第三步检查从节点日志。如果看到大量MASTER - REPLICA sync started的重复记录说明确实在反复全量。第四步用monitor或INFO观察主节点写流量大小估算积压缓冲区的冲刷速度。根因通常有三类repl-backlog-size太小。断线期间写命令把缓冲区写满从节点重连后 offset 已被覆盖被迫全量。网络抖动加上repl-timeout太短。主从之间未必真正断开只是延迟稍微大了点就被误判超时触发了重连又因为没有合理增量窗口而全量。主从切换后 replid 不匹配。旧从节点无法增量续传只能全量。解决方向也比较明确把repl-backlog-size调大至少满足“最坏断线时长内的写字节数”。调整repl-timeout跨机房或延迟偏大的环境建议调到 30 甚至 60 秒以上。优先升级到较新的 Redis 版本充分享受 PSYNC 2.0 的 replid2 机制带来的好处。5.2 主从延迟越来越大业务读到旧数据另一个高频问题主从 offset 始终对不上延迟秒数持续增长。这个问题的根子通常有两个方向。第一从节点机器性能不足。从节点在同步数据的同时还在对外提供读流量如果 CPU、内存带宽成为瓶颈它处理复制命令的速度就会跟不上主节点产生写入的速度offset 差值像滚雪球一样放大。解法不是调 Redis 参数而是给从节点扩容或者调整读写分离策略把压力挪走一部分。第二大键操作的阻塞。主节点一条命令删掉一个很大的集合或者执行了KEYS这类全库扫描都会阻塞从节点的复制数据处理。虽然从节点处理复制流是异步的但碰到超大命令反序列化、内存分配同样耗时。我遇到过因为某个业务模型里塞了超大 Hash 结构主从延迟直接飙到几十秒。后面在业务侧做了大键拆分才根除隐患。另外要注意主从延迟不一定是“同步慢”也可能是主节点写 QPS 太高从节点积压了太多命令没处理完。可以用INFO stats里的total_net_input_bytes和total_net_output_bytes对比主从两侧的网络吞吐。如果主节点发送速率远大于从节点接收速率大概率是从节点处理不过来。5.3 复制风暴与多从节点同时全量同步复制风暴这个词很多人是在一次真实故障之后才记住的。场景是这样的主节点挂了哨兵把一个从节点提升为新主节点原来的其他从节点全都需要重新指向新主节点。如果所有从节点几乎同时发起全量同步新主节点就要同时生成多份 RDB 快照瞬间把 CPU、内存、网络全部打满反过来拖垮正在对外服务的新主节点甚至引发新一轮故障。更隐蔽的复制风暴发生在级联复制场景。主节点有一个从节点 BB 又有一个从节点 C。主节点重启导致 replid 变化B 要全量C 因为 B 的数据发生改变也可能跟着全量。一层一层放大之后一次主节点故障导致很多节点同时做全量。缓解复制风暴有几个实操手段控制从节点数量。如果确实需要很多副本考虑用级联复制让中间层从节点承担复制转发不对外提供读流量。让从节点的全量同步错开。Redis 没有现成的“随机延迟”参数但可以在自动化脚本里为每台从节点设置不同的repl-diskless-sync-delay或者手动在不同时间点触发重连。优先保证新主节点的 IO 稳定。故障切换后的短时间内可以把min-replicas-to-write临时设为 1 并观察延迟避免新主节点在复制尚未稳定时就被写流量打爆。从节点可以先不提供读取服务等复制稳定之后再开放流量。这个可以通过调整负载均衡权重实现让应用层暂时不访问它。最后再分享一个小技巧做任何主从变更之前先看全量同步的历史频次。INFO REPLICATION里有sync_full统计字段如果这个数字在快速上涨不要急着扩容或加节点先回头查 backlog 配置和网络质量。否则你加再多的节点也都会被复制风暴吞掉。我在实际维护中发现遇到主从同步问题很多人第一反应是重启节点这往往是下下策。重启能缓解一时症状但会掩盖真正的根因。退一步说即使配置不做复杂优化也一定把INFO REPLICATION、慢日志、监控告警三条链路提前建好。数据同步这块看起来是 Redis 内部很底层的事情但它决定了你在高并发读写和故障切换时系统能不能站得住。把全量同步和增量同步的机制理解透遇到问题心里才有底。