☰
Redis主从复制深度解析:全量同步与增量同步机制拆解
2026/10/23 3:59:07 网站建设 项目流程

说到 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 文件。生成快照有两种方式:基于磁盘的传统方式,以及无盘复制方式。

传统方式下,主节点执行BGSAVE,fork 一个子进程出来,由子进程把内存数据写成 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,主节点再把缓冲区里的部分命令补发给它。

所以,一个完整的全量同步流程实际上包含三个阶段:

  1. 主节点生成 RDB 并传输给从节点。
  2. 在此期间,主节点把新写命令持续记录到积压缓冲区。
  3. 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 测算,一般不要低于 32mb
repl-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_status:up表示与主节点连接正常,down表示断开。长时间down立刻查网络和 repl-timeout 配置。
  • master_sync_in_progress:1 表示正在做全量同步,长期不为 0 要警惕同步卡住。
  • master_last_io_seconds_ago:上一次和主节点通信距现在多少秒,持续增大说明主从可能断连或主节点负载太高。
  • slave_repl_offset:从节点当前的复制偏移量。

在主节点上执行INFO REPLICATION,重点看connected_slaves和每个从节点的lag:

connected_slaves:2 slave0:ip=192.168.1.20,port=6379,state=online,offset=123456789,lag=0 slave1:ip=192.168.1.30,port=6379,state=online,offset=123456789,lag=2

lag是从节点延迟秒数,如果持续超过 10 秒,就要看是网络问题还是从节点负载太高。另外,主节点的INFO REPLICATION里如果出现state=offline,说明这个从节点已经失联,需要检查从节点日志或网络链路。

用redis-cli --stat也能看实时状态,但INFO REPLICATION是排查主从问题最简单有效的手段,建议优先掌握。

5. 实战中经常踩的坑与排查实录

5.1 全量同步频繁触发,整个集群被拖垮

这是我在维护一组 Redis 主从时遇到的最典型故障,也是社区里被问得最多的问题。

现象:主从连接正常,但从节点每隔几分钟就断线重连,每次重连都触发全量同步,主节点 CPU 飙升、带宽打满、QPS 抖动。

排查步骤:

  • 第一步,登录主节点执行INFO REPLICATION,观察从节点的 state 和 offset 变化。如果 offset 经常回退,说明从节点确实在反复全量同步。
  • 第二步,查看主节点慢日志和内存。如果主节点同时在做 BGSAVE 生成 RDB,CPU 会有明显峰值,这是全量同步的典型特征。
  • 第三步,检查从节点日志。如果看到大量MASTER <-> REPLICA sync started的重复记录,说明确实在反复全量。
  • 第四步,用monitor或INFO观察主节点写流量大小,估算积压缓冲区的冲刷速度。

根因通常有三类:

  1. repl-backlog-size太小。断线期间写命令把缓冲区写满,从节点重连后 offset 已被覆盖,被迫全量。
  2. 网络抖动加上repl-timeout太短。主从之间未必真正断开,只是延迟稍微大了点就被误判超时,触发了重连,又因为没有合理增量窗口而全量。
  3. 主从切换后 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、内存、网络全部打满,反过来拖垮正在对外服务的新主节点,甚至引发新一轮故障。

更隐蔽的复制风暴发生在级联复制场景。主节点有一个从节点 B,B 又有一个从节点 C。主节点重启导致 replid 变化,B 要全量,C 因为 B 的数据发生改变也可能跟着全量。一层一层放大之后,一次主节点故障导致很多节点同时做全量。

缓解复制风暴有几个实操手段:

  • 控制从节点数量。如果确实需要很多副本,考虑用级联复制,让中间层从节点承担复制转发,不对外提供读流量。
  • 让从节点的全量同步错开。Redis 没有现成的“随机延迟”参数,但可以在自动化脚本里为每台从节点设置不同的repl-diskless-sync-delay,或者手动在不同时间点触发重连。
  • 优先保证新主节点的 IO 稳定。故障切换后的短时间内,可以把min-replicas-to-write临时设为 1 并观察延迟,避免新主节点在复制尚未稳定时就被写流量打爆。
  • 从节点可以先不提供读取服务,等复制稳定之后再开放流量。这个可以通过调整负载均衡权重实现,让应用层暂时不访问它。

最后再分享一个小技巧:做任何主从变更之前,先看全量同步的历史频次。INFO REPLICATION里有sync_full统计字段,如果这个数字在快速上涨,不要急着扩容或加节点,先回头查 backlog 配置和网络质量。否则你加再多的节点,也都会被复制风暴吞掉。

我在实际维护中发现,遇到主从同步问题,很多人第一反应是重启节点,这往往是下下策。重启能缓解一时症状,但会掩盖真正的根因。退一步说,即使配置不做复杂优化,也一定把INFO REPLICATION、慢日志、监控告警三条链路提前建好。数据同步这块看起来是 Redis 内部很底层的事情,但它决定了你在高并发读写和故障切换时系统能不能站得住。把全量同步和增量同步的机制理解透,遇到问题心里才有底。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询