概念与结构
Redis 主从复制指的是:让一个 Redis 主节点,也就是 master,把自己的数据同步给一个或多个从节点,也就是 replica。以前很多资料会写 slave,现在更推荐使用 replica 这个说法,但学习时看到 slave 也要能对应上。
主从复制最基本的结构是:
master
├── replica 1
├── replica 2
└── replica 3
在这种结构中,master 通常负责处理写请求,replica 负责接收主节点同步过来的数据,并承担一部分读请求。这样做的核心目的有三个:第一是读写分离,第二是提高可用性,第三是扩展系统的读取能力。Redis 官方文档也说明,基础复制模型是 leader-follower,也就是 master-replica 模型,replica 会尽量成为 master 的精确副本。
实际理解时可以把主从复制想成主库负责写,从库负责跟随。客户端写入数据时,一般写到主节点;主节点接收到写命令后,再把这些变更同步给从节点。客户端读取数据时,可以从从节点读取,从而减轻主节点压力。
不过要注意,Redis 默认使用异步复制,主节点不会每次写入都等待所有从节点确认完成后再返回结果,所以主从之间可能存在短暂的数据延迟。
安全性、只读与传输延迟
- 安全性:Redis 主从复制可以提高系统可用性,但它不是严格意义上的“备份方案”。如果主节点发生误删、误写,这些错误操作也可能同步到从节点,导致从节点也会同步主节点,进而删除数据。所以如果数据安全非常重要,不能只依赖主从复制,还要考虑 RDB、AOF、定期备份、权限控制和运维策略。
官方文档里也特别提醒过一种情况:如果主节点关闭持久化,并且崩溃后自动重启,那么主节点可能以空数据集启动,从节点继续复制它时,也可能被同步成空数据集。
-
只读:从节点
replica通常配置为只读模式,由replica-read-only控制,它能防止误把写请求打到从节点,降低主从数据不一致的风险。如果主动尝试在从节点中修改数据,Redis 是会直接返回错误信息的。 毕竟从节点来写数据,那就不算从节点了,那就是翻身做主人了! -
传输延迟:由于 Redis 默认是异步复制,写命令先在主节点执行,然后再通过复制流发送给从节点。如果网络波动、从节点处理较慢、复制缓冲区压力较大,就可能出现从节点短暂落后主节点的情况。读写分离场景下,如果业务对“刚写完马上读到最新数据”要求非常高,就不能随便把读请求分流到从节点。
拓扑结构
主从复制不只有“一主一从”。根据系统规模和用途,常见拓扑可以分成几类。
- 单主单从:它是最简单的结构,一个 master 对应一个 replica。这个结构容易理解,也适合学习和小规模部署。
- 一主多从:一个 master 同步给多个 replica,这是比较常见的读扩展结构。多个从节点可以承担读请求,减轻主节点压力。
- 链式或树状复制:Redis 支持 replica 连接到另一个 replica,形成级联复制结构。官方文档也提到,replica 可以接受其他 replica 的连接,从而形成类似级联的复制结构。
这种结构的好处是可以减轻 master 的出口压力,但问题是链路变长后,复制延迟可能变大。越靠后的从节点,越可能落后主节点,具体要实现哪种要根据实际业务要求。
- 第四种是在哨兵或集群场景中的复制关系。Sentinel 主要负责监控和故障转移,Cluster 则用于分片和高可用。它们不是主从复制本身,但实际项目中经常和主从复制配合出现。
总结图片
基本流程
Redis 主从复制的基本流程可以理解成三句话:先建立连接,再同步数据,最后持续跟随。
当一个 replica 想要复制 master 时,它会先连接 master,并发起复制请求。Redis 使用 PSYNC 机制进行同步判断。主节点会根据 replica 提供的信息判断:这次可以部分复制,还是必须全量复制。官方文档说明,如果部分重同步不可行,replica 会请求全量重同步;如果可行,就只获取断开期间缺失的那部分命令流。
复制流程可以拆成下面几步:
- replica 连接 master,主动发起 PSYNC
- master 判断同步方式:是选择全量复制或部分复制
- 如果需要全量复制,master 生成 RDB 快照
- master 将 RDB 发送给 replica
- replica 加载 RDB 数据
- master 再发送生成 RDB 期间积累的增量命令 (因为replica在加载RDB文件时 master 可能还会产生新的文件和命令~)
- 进入稳定阶段,master 持续把写命令传播给 replica (建立 TCP 长连接)
- 进入稳定阶段后,如果从节点也开启了 AOF,则从节点也要进行响应的 AOF 判断,在必要时执行
bgrewriteaof命令
首次建立主从关系时,通常会走全量复制。因为 replica 没有旧的同步历史,master 只能给它一份完整数据。
之后如果网络短暂中断再恢复,就有机会走部分复制,只补上中间缺失的命令。如何判断是走部分还是全量复制呢?下面章节会有详细说明~
replid 和 offset 的作用
Redis 主从复制要解决两个判断问题:
- 我们是不是同一条复制历史?
- 已经同步到哪里了?
第一个问题对应着 replid;第二个问题对应 offset
-
replid 可以理解为“复制历史身份”。官方文档说每个 master 都有一个 replication ID,它标记某一段数据集历史。master 和 replica 如果处在同一条复制历史上,就会共享对应的 replication ID。当从节点从属于主节点的话,它的 replication ID 就是主节点的 replication ID。相当于主节点靠 replication ID 来认小弟~
-
offset 可以理解为“复制进度”。master 产生复制流时,offset 会随着复制流字节数不断增长。replica 会记录自己已经处理到哪个 offset。如果从节点的 offset 与主节点的 offset 不一致,说明就有数据没有同步过来了(通常是这样)。官方文档也说明,replication ID + offset 可以标识 master 数据集的一个精确版本。
举个栗子:
master replid = abc, offset = 1000;replica replid = abc, offset = 800
这说明它们属于同一条复制历史,但 replica 落后了 200 的复制流。如果 master 的复制积压缓冲区里还保留着 offset 800 之后的数据,那么 replica 只需要部分复制,补上缺失的那一段就可以。
如果 replid 不一样,或者 offset 太旧,master 已经没有保留那段复制流,那么就只能走全量复制。
此处提到了积压缓冲区,我们来看看它是什么~
积压缓冲区
积压缓冲区就是当主从复制建立后,master 会持续把写命令传播给 replica,同时也把这些写命令对应的复制流记录到复制积压缓冲区里。里面保存的是最近一段写命令的复制流和offset 开始值与最大值,直到从节点重新连接。
重新连接后,主节点会检查 replication ID + offset,看看这个从节点还是不是之前的小弟。如果 replication ID 一致,就说明是曾经复制过的小弟,就会检查它的 offset,如果从节点的 offset 太旧了,主节点的 offset 的最小值都比从节点的 offset 大,此时就会丢弃,重新进行全量复制。(因为主节点在积压缓冲区的范围是有限的,若数据比较多,缓冲区中会丢弃更加久的数据,保留最近的数据,offset 会慢慢变大)
全量复制
全量复制,也叫 full resynchronization,意思是从节点需要重新获得一份完整的数据集。
典型触发场景有三种:
- 第一次建立主从关系
- replica 落后太多,offset 不在 backlog 范围内
- replid 不匹配,说明不是同一条复制历史(同一个小弟~)
全量复制流程大致是:
- replica 发起同步请求
- master 判断无法部分复制
- master 生成 RDB 快照
- master 将 RDB 文件发送给 replica
- replica 加载 RDB
- master 补发生成 RDB 期间收到的写命令
master 在生成 RDB 的同时,仍然可能继续接收新的写命令。这些新写命令不能丢,所以 master 会把这段时间的增量命令缓冲起来。等 replica 加载完 RDB 后,再把这些增量命令发过去。Redis 官方文档对全量同步的描述也是:master 后台生成 RDB 文件,同时缓冲新的写命令,RDB 传输并加载完成后,再把缓冲命令发送给 replica。
全量复制的缺点是开销比较大。它涉及 RDB 生成、RDB 传输、replica 加载整个数据集。如果数据量很大,可能带来磁盘、网络、CPU、内存等压力。
部分复制
部分复制,也叫 partial resynchronization,意思是 replica 不需要重新同步整个数据集,只需要补上自己断线期间缺失的那部分命令。
部分复制通常发生在 replica 和 master 短暂断开后重新连接时。replica 会向 master 发送自己保存的 replid 和 offset,master 根据这些信息判断能不能只补增量。官方文档说明,replica 连接 master 时会使用 PSYNC 发送旧 master 的 replication ID 和已经处理的 offset;如果 master 缓冲区里还有对应数据,就可以发送增量部分,否则走全量同步。
部分复制的判断可以简化成:
如果 replid 一样,并且 offset 还在复制积压缓冲区范围内:
走部分复制
只补缺失命令
否则:
走全量复制
重新同步完整数据
部分复制的优点很明显:更快、更轻量、网络开销更小。对于短暂网络抖动、从节点短暂掉线这种情况,部分复制非常重要。
实时复制
当全量复制或部分复制完成后,主从关系进入稳定阶段。这个阶段 master 会继续接收客户端写命令,并把这些写命令传播给 replica。replica 按顺序执行收到的命令,从而让数据尽量跟上 master。这个阶段也可以叫命令传播阶段,或者增量复制阶段。
实时复制依赖的是主从节点之间的 TCP 长连接传输~
基本流程:
- 客户端写入 master
- master 执行写命令
- master 将写命令传播给 replica
- replica 顺序执行写命令
- 主从数据继续保持接近一致
在进行实时复制时,需要保证连接处于持续可用的状态,所以引入了“心跳包”的机制:
- 主节点默认每隔一段时间就会给从节点发送一个 ping 命令,从节点收到就回 pong
- 从节点默认每个一个时间周期就会给主节点发送一个特定的请求,会上报当前复制主节点数据的进度(offset)
实时复制不代表强一致。因为复制仍然以异步为主,网络和执行时间都会带来延迟。主从数据持续接近一致,而不是任何时刻都绝对一致。
主从连接与心跳机制的区别
主从连接和心跳机制有联系,但不是一回事。主从连接强调的是 replica 与 master 建立复制通道,完成握手并确认同步方式;
心跳机制强调的是复制关系建立之后,双方持续保持联系,replica 周期性向 master 汇报同步进度,master 借此判断从节点是否在线、是否落后。如果心跳异常或连接中断,从节点会重新连接 master,并根据 replid + offset 尝试进行部分复制。
全量复制的无硬盘模式优化
无硬盘复制即 diskless replication
正常全量复制时,master 需要生成 RDB 文件。传统方式是先把 RDB 写到磁盘,然后再从磁盘读取并发送给 replica,流程大概为:
- master 生成 RDB 到磁盘
- master 从磁盘读取 RDB
- 发送给 replica
- replica 加载 RDB
无硬盘复制,也就是 diskless replication,是对全量复制阶段的优化。它的核心思想是:master 不先把 RDB 落盘,而是直接通过 socket 把 RDB 流式发送给 replica。在 diskless replication 中子进程会直接通过网络把 RDB 发给 replicas,而不使用磁盘作为中间存储。
可以这样对比:
无硬盘复制的好处是减少磁盘 I/O,尤其适合磁盘压力大、数据集较大、多个 replica 同步的场景。不过它不一定在所有场景都更优。如果网络条件差,或者需要更稳定的落盘中间文件,普通方式也可能更合适。
另外要注意:无硬盘复制优化的是全量复制阶段,不是部分复制阶段。部分复制本来就是补增量命令,不需要重新传完整 RDB。
runid 与 replid 的区别
runid 和 replid 都像“身份标识”,但它们不是一个东西。
runid 是 Redis 实例自身的运行时 ID。你可以把它理解成“这个 Redis 进程的身份证”。Redis INFO 命令文档中,run_id 被描述为用于识别 Redis server 的随机值,也会被 Sentinel 和 Cluster 使用。
replid 是复制历史 ID。它不是用来识别 Redis 进程本身,而是用来识别某一段复制历史。主从复制、PSYNC、部分复制判断时,更关键的是 replid + offset。
二者可以这样区分:
| 对比项 | runid | replid |
|---|---|---|
| 关注对象 | Redis 实例本身 | 复制历史,redis 服务重启后就不一样了 |
| 用途 | 识别 server 实例 | 判断复制历史、辅助部分复制 |
| 是否直接决定部分复制 | 不是关键 | 是关键参考 |
| 常见位置 | INFO server 里的run_id |
INFO replication 里的master_replid |
Redis 还可能维护两个 replication ID:当前 master_replid 和 master_replid2。官方文档说明,第二个 replication ID 是为了故障转移后的部分重同步服务的;当 replica 被提升为 master 后,它会保留旧 master 的复制 ID 信息,从而尽量让其他 replica 不必全部重新全量复制。
最小实践配置
如果只是做学习 demo,可以启动两个 Redis 实例。假设主节点端口是 6379,从节点端口是 6380。
从节点配置中可以写:
replicaof 127.0.0.1 6379
或者在从节点运行时执行:
REPLICAOF 127.0.0.1 6379
Redis 官方文档也说明,配置基础复制可以在 replica 配置文件里添加 replicaof <master-ip> <master-port>,也可以通过 REPLICAOF 命令让实例开始同步 master。
常用观察命令:
INFO replication
ROLE
INFO replication 可以看到当前实例的复制角色、复制 ID、复制偏移量、连接的从节点数量等信息。ROLE 可以直接查看当前节点是 master 还是 replica,以及复制状态。Redis 官方文档也提到,INFO replication 和 ROLE 都能提供当前复制相关参数。
从节点晋升主节点
从节点断开主节点有两种情况
- 从节点主动断开:使用
slaveof no one,这时候从节点就能晋升为主节点,这意味着我们主动干预了 Redis 的结构 - 主节点挂了:这个时候从节点不会主动晋升为主节点,它必须通过人工干预的方式,将主节点重新启动
复习总结
Redis 主从复制的核心,是 master 持续把自己的数据变化同步给 replica。它常用于读写分离、提高可用性和扩展读能力,但 Redis 默认是异步复制,所以主从之间可能有短暂延迟。
复制流程可以理解为:replica 先连接 master,然后通过 PSYNC 请求同步。master 根据 replid 和 offset 判断是全量复制还是部分复制。全量复制会发送完整 RDB,再补发期间的增量命令;部分复制只补断开期间缺失的命令。稳定后,master 持续把写命令传播给 replica,形成实时复制。
replid 表示复制历史,offset 表示同步进度。runid 表示 Redis 实例自身身份,不要和 replid 混淆。无硬盘复制是全量复制阶段的优化,它通过 socket 直接发送 RDB,减少磁盘 I/O。