一文吃透 Redis 主从复制:从"为什么需要"到全量 / 部分 / 实时三种复制(含完整流程图)
前面我们聊过 Redis 持久化(RDB 快照与 AOF 日志),持久化解决的是单机断电数据不丢的问题。但如果这台机器本身宕机了呢?这就是本文要讲的:主从复制(Replication)——它是 Redis 高可用的基石,哨兵(Sentinel)和集群(Cluster)都是构建在复制的基础之上的。
一、为什么要主从复制?
1.1 单机 Redis 的两大痛点
一台单独的 Redis 服务器,存在典型的单点问题:
| 痛点 | 说明 |
|---|---|
| ① 可用性不高 | 只有一台机器,它一旦宕机,整个缓存服务就瘫痪了,业务直接受损 |
| ② 性能有限 | 所有的读请求 + 写请求都压在一台机器上,CPU、内存、网络都有上限,扛不住高并发 |
1.2 主从复制的解决思路
Redis 提供了复制功能,实现相同数据的多个 Redis 副本(Replica):
- 数据多副本 → 故障恢复:主节点挂了,从节点上还有一份完整数据,可以顶上提供服务(配合哨兵可以自动完成故障转移);
- 读写分离 → 分摊压力:主节点负责写,从节点负责读,读压力被分摊到多台从节点上,降低主节点的访问压力。
1.3 主从复制的三个基本特点
- 参与复制的实例分为主节点(master)和从节点(slave);
- 每个从节点只能有一个主节点,而一个主节点可以同时有多个从节点;
- 复制的数据流是单向的——只能由主节点流向从节点,从节点上的修改主节点无法感知。
二、主从复制的详细操作
2.1 如何建立复制(三种配置方式)
主节点的配置不需要任何改动,只需要在从节点上指明"我的主节点是谁",三种方式任选:
# 方式 1:配置文件中加入(随 Redis 启动生效)slaveof{masterHost}{masterPort}# 方式 2:redis-server 启动命令时加入redis-server /etc/redis/redis-slave.conf--port6380--slaveof127.0.0.16379# 方式 3:运行时直接使用 redis 命令127.0.0.1:6380>slaveof127.0.0.16379验证是否生效,在主从节点分别执行:
127.0.0.1:6379>info replication# 主节点看到 role:master, connected_slaves:N127.0.0.1:6380>info replication# 从节点看到 role:slave, master_link_status:up补充两个相关操作:
- 断开复制:从节点执行
slaveof no one,断开后从节点晋升为主节点,原有数据不会丢弃,只是不再同步主节点的新变化;- 切主操作:执行
slaveof {newMasterIp} {newMasterPort},流程为:断开旧主 → 连接新主 →删除自身全部数据→ 从新主重新复制。
2.2 建立复制的完整流程(六个步骤)
在从节点上执行slaveof之后,内部会依次经历六个步骤:
- 保存主节点信息:从节点记录主节点的 ip / port,此时
master_link_status还是down; - 主从建立网络连接:从节点内部每秒运行的定时任务发现新主节点后,尝试建立 TCP 连接;连不上会无限重试,直到成功或手动停止复制;
- 发送 ping 命令:确认主节点在应用层上工作正常;若 pong 超时,断开连接等下轮重连;
- 权限验证:主节点设置了
requirepass时,从节点需要通过masterauth配置一致的密码,验证失败则复制停止; - 同步数据集:把主节点当前持有的所有数据全部发给从节点,这是最耗时的一步,分为全量同步和部分同步两种情况(见第三节);
- 命令持续复制:数据同步完成后,主节点会把后续的每一条写命令持续发送给从节点,保证主从数据一致。
关键概念:runid(复制 id)+ offset(偏移量)共同标识了一份"数据集"——如果两个节点的 runid 和 offset 都相同,它们持有的数据就一定相同。这个概念是理解全量 / 部分复制的钥匙。
三、三种复制方式(重点)
Redis 使用psync命令完成主从数据同步,语法:
PSYNC replicationid offsetreplid为?且offset为-1→ 尝试进行全量复制(第一次复制时从节点啥也不知道,只能这么发);replid和offset为具体数值 → 尝试进行部分复制(从节点之前复制过,记得自己的"学习进度")。
三种复制方式:全量复制、部分复制、实时复制。下面逐一拆解。
3.1 全量复制 —— 首次复制必须经历
全量复制是主从第一次建立复制时必须经历的阶段:主节点把全部数据一次性以 RDB 的形式发给从节点。
流程详解(对应图中 ①-⑨):
- 从节点发送
psync ? -1(第一次复制,没有主节点的 runid 和 offset); - 主节点解析出要做全量复制,回复
+FULLRESYNC {runid} {offset}; - 从节点接收并保存主节点的 runid 和 offset;
- 主节点执行
bgsave,生成 RDB 文件; - 主节点把 RDB 文件发送给从节点,从节点保存到本地硬盘;
从 Redis 2.8.18 开始支持无盘复制(diskless):主节点生成 RDB 时不落磁盘,直接通过网络发给从节点,省去写硬盘 + 读硬盘的开销;
- 生成 RDB 期间主节点仍在响应写命令,这些新写命令暂存到缓冲区,等从节点保存完 RDB 后再补发过去(仍按 RDB 二进制格式追加写入),保证一致性;
- 从节点清空自身原有旧数据;
- 从节点加载 RDB,得到与主节点一致的数据;
- 如果从节点开启了 AOF,再执行
bgrewriteaof,得到最近的 AOF 文件。
⚠️全量复制成本非常高:主节点 bgsave 的时间 + RDB 网络传输的时间 + 从节点清空旧数据的时间 + 加载 RDB 的时间。数据量越大代价越大,所以应尽量避免对已有大量数据集的 Redis 进行全量复制——这正是部分复制存在的意义。
3.2 部分复制 —— 网络闪断后的"断点续传"
部分复制是针对全量复制过高开销做出的优化措施:从节点复制期间如果出现网络闪断、命令丢失等异常,重新连上后只补发缺失的那一小段数据,开销极小。
流程详解(对应图中 ①-⑥):
- 主从之间网络中断,超过
repl-timeout时间后,主节点认为从节点故障,断开复制连接; - 中断期间主节点依然响应命令,但这些复制命令无法及时发给从节点,于是**暂时滞留在「复制积压缓冲区」**中;
- 网络恢复后,从节点重新连上主节点;
- 从节点把之前保存的replid + offset作为参数发送
psync {replid} {offset},请求部分复制; - 主节点校验后,根据 offset 去复制积压缓冲区查找数据,回复
+CONTINUE; - 主节点把缺失的那部分数据补发给从节点,主从重新一致。
核心:复制积压缓冲区(repl-backlog-buffer)
- 保存在主节点上的一个固定长度队列,默认 1MB;主节点有从节点连接时创建;
- 主节点响应写命令时,不但发给从节点,还会同时写入这个缓冲区;
- 本质是先进先出的环形队列(相当于数组实现的环形缓冲区),只保存最近已复制的数据;
- 可用偏移量范围 =
[repl_backlog_first_byte_offset, first_byte_offset + repl_backlog_histlen](可用info replication查看)。
⚠️如果从节点需要的 offset 已经超出缓冲区范围(断线太久、被环形队列覆盖了),缓冲区里已经没有它要的数据 →无法部分复制,只能退化成全量复制。写量大的场景可以调大
repl-backlog-size来降低这种退化概率。
3.3 实时复制 —— 长连接 + 心跳保活
全量/部分复制完成后,主从进入命令实时复制阶段:
- 主节点把自己收到的每一条修改操作,通过 TCP 长连接源源不断传给从节点;
- 从节点执行相同命令,实时修改自身数据,与主节点保持一致。
这条长连接依靠应用层实现的心跳机制来维护(不是 TCP 自带的心跳):
| 角色 | 心跳行为 | 默认频率 |
|---|---|---|
| 主节点 → 从节点 | 发送ping,判断从节点存活性和连接状态 | 每10 秒 |
| 从节点 → 主节点 | 发送replconf ack {offset},上报当前复制偏移量 | 每1 秒 |
如果主节点发现从节点通信延迟超过repl-timeout(默认 60 秒),则判定从节点下线,断开复制连接;从节点恢复后,心跳机制继续进行。
3.4 三种方式的关系
一句话总结:先全量(第一次),后实时(日常),出故障再部分复制补齐。
| 维度 | 全量复制 | 部分复制 | 实时复制 |
|---|---|---|---|
| 触发时机 | 首次建立复制 / offset 超出缓冲区范围 | 网络闪断恢复后,offset 仍在缓冲区内 | 数据同步完成之后持续进行 |
| psync 参数 | psync ? -1 | psync {replid} {offset} | — |
| 主节点响应 | +FULLRESYNC | +CONTINUE | 持续推送写命令 |
| 传输内容 | 完整 RDB 快照 | 缓冲区中缺失的那段命令 | 后续每一条写命令 |
| 开销 | 极高(bgsave + 传输 + 清空 + 加载) | 很小(只补缺失部分) | 平稳(逐条增量) |
四、主从复制的拓扑结构
复制的拓扑可以单层也可以多层,常见的有三种:
| 拓扑 | 结构 | 适用场景 | 注意点 |
|---|---|---|---|
| 一主一从 | 最简单,1 个主 + 1 个从 | 主节点宕机时从节点提供故障转移支持 | 可只在从节点开 AOF,避免持久化干扰主节点性能;主节点关闭持久化时,宕机后要避免自动重启 |
| 一主多从(星形) | 1 个主 + N 个从 | 读并发量大:读命令负载均衡到多个从节点;耗时读命令可指定专用从节点 | 写并发量大时,写命令要发给每个从节点,反而加重主节点负载 |
| 树形主从(分层) | 从节点还可以作为下层节点的主节点继续向下复制 | 主节点需要挂载多个从节点时,避免性能干扰 | 引入复制中间层,有效降低主节点负载和传送的数据量 |
五、其他实用配置
- 安全性:主节点设置
requirepass后,从节点必须配置相同的masterauth,否则无法通过权限验证发起复制; - 只读模式:从节点默认
slave-read-only=yes。因为复制是单向的,从节点上的修改主节点感知不到,会造成主从数据不一致,线上不建议关闭; - 传输延迟:
repl-disable-tcp-nodelay默认no(开启 TCP_NODELAY,命令小包及时发送,延迟小但占带宽,适合同机房);设为yes则合并小包节省带宽(约 40ms 间隔),延迟变大,适合跨机房。
六、总结与重点回顾
- 主从复制解决的是单点问题:单节点可用性不高、性能有限;通过数据多副本实现故障恢复,通过读写分离分摊压力;
- 配置很简单:主节点不动,从节点加
slaveof {masterHost} {masterPort}即可;数据流永远单向(主 → 从); - 建立复制六步走:保存主节点信息 → 建立连接 → ping → 权限验证 → 同步数据集 → 命令持续复制;
- 三种复制方式:
- 全量复制(
psync ? -1→+FULLRESYNC):首次复制必须经历,bgsave 生成 RDB 全量传输,成本极高; - 部分复制(
psync {replid} {offset}→+CONTINUE):靠复制积压缓冲区(默认 1MB 环形队列)补发断线期间的数据,offset 超出缓冲区范围则退化为全量; - 实时复制:TCP 长连接持续推送写命令,主 10 秒 ping 一次、从 1 秒上报一次 offset 的心跳机制保活;
- 全量复制(
- 核心概念:
replid + offset共同标识一份数据集,相当于主从之间对齐"学习进度"; - 哨兵和集群都是在主从复制的基础上构建的——理解了复制,后面学高可用架构就顺理成章了。
如果本文对你有帮助,欢迎点赞、收藏、评论交流!