文章目录
-
- [Redis 主从复制:数据同步的原理与实现](#Redis 主从复制:数据同步的原理与实现)
- 一、前言
- 二、主从复制是什么
-
- [2.1 基本概念](#2.1 基本概念)
- 三、建立和断开复制
-
- [3.1 建立复制的三种方式](#3.1 建立复制的三种方式)
- [3.2 配置示例](#3.2 配置示例)
- [3.3 查看复制状态](#3.3 查看复制状态)
- [3.4 断开复制](#3.4 断开复制)
- [3.5 切换主节点](#3.5 切换主节点)
- [3.6 安全性配置](#3.6 安全性配置)
- [3.7 只读配置](#3.7 只读配置)
- [3.8 传输延迟控制](#3.8 传输延迟控制)
- 四、主从拓扑结构
-
- [4.1 一主一从](#4.1 一主一从)
- [4.2 一主多从(星形结构)](#4.2 一主多从(星形结构))
- [4.3 树形主从结构](#4.3 树形主从结构)
- 五、复制的核心原理
-
- [5.1 建立复制的完整流程](#5.1 建立复制的完整流程)
- [5.2 runid、replid、offset 的区别](#5.2 runid、replid、offset 的区别)
-
- 1)runid:实例本次运行的唯一标识
- 2)replid:复制历史的唯一标识
- 3)offset:复制偏移量
- [4)replid + offset 共同标识一份复制数据历史中的位置](#4)replid + offset 共同标识一份复制数据历史中的位置)
- [5.3 master_replid 和 master_replid2](#5.3 master_replid 和 master_replid2)
- [5.4 psync 机制](#5.4 psync 机制)
- [5.5 全量复制、部分复制、实时复制](#5.5 全量复制、部分复制、实时复制)
- 六、全量复制
- 七、部分复制
-
- [7.1 为什么需要部分复制](#7.1 为什么需要部分复制)
- [7.2 部分复制的触发条件](#7.2 部分复制的触发条件)
- [7.3 部分复制的完整流程](#7.3 部分复制的完整流程)
- [7.4 复制积压缓冲区](#7.4 复制积压缓冲区)
- [7.5 复制积压缓冲区范围计算](#7.5 复制积压缓冲区范围计算)
- [7.6 如何设置 repl-backlog-size](#7.6 如何设置 repl-backlog-size)
- 八、实时复制(命令传播)
-
- [8.1 命令传播](#8.1 命令传播)
- [8.2 心跳机制](#8.2 心跳机制)
-
- [主节点向从节点发送 PING](#主节点向从节点发送 PING)
- [从节点向主节点发送 REPLCONF ACK](#从节点向主节点发送 REPLCONF ACK)
- [8.3 repl-timeout](#8.3 repl-timeout)
- [8.4 最小从节点写入保护](#8.4 最小从节点写入保护)
- 九、总结
-
- [9.1 主从复制解决的问题](#9.1 主从复制解决的问题)
- [9.2 主从复制的特点](#9.2 主从复制的特点)
- [9.3 runid、replid、offset 重点回顾](#9.3 runid、replid、offset 重点回顾)
- [9.4 全量复制 vs 部分复制](#9.4 全量复制 vs 部分复制)
- [9.5 常见配置速查](#9.5 常见配置速查)
- [9.6 主从复制的不足](#9.6 主从复制的不足)
Redis 主从复制:数据同步的原理与实现
一、前言
💬 这一篇讲什么:Redis 的主从复制机制
🚀 核心内容:
- 如何建立和断开主从复制关系?
- 三种主从拓扑结构各有什么特点?
runid、replid、offset分别是什么?- psync 机制是什么?如何决定全量复制还是部分复制?
- 全量复制、部分复制、实时复制的完整流程是怎样的?
上一篇讲完了 Redis 事务,这一篇进入 Redis 高可用体系的第一块基石 ------ 主从复制。
在分布式系统中,为了解决单点问题,通常会把数据复制多个副本部署到其他服务器上,从而满足故障恢复、读写分离、负载均衡等需求。Redis 也提供了复制功能,可以实现同一份数据在多个 Redis 实例之间同步。
复制功能是 Redis 高可用体系的基础。后面的哨兵(Sentinel)和集群(Cluster)都建立在复制能力之上。所以这一篇的重点不是死记命令,而是理解复制的流程和原理。
可以用一个课堂里的例子理解:
如果学生比较少,一个老师既可以授课,也可以答疑;但是随着学生越来越多,一个老师就应付不过来了。这时就需要配几个助教老师。助教老师从授课老师这里获取知识,再协助授课老师答疑。
Redis 主从复制也是类似的:
- 主节点就像授课老师,负责写入和产生最新数据。
- 从节点就像助教老师,从主节点同步数据,并承担读请求。
- 如果主节点出问题,从节点还可以作为后续故障切换的基础。
二、主从复制是什么
2.1 基本概念
Redis 提供了复制(Replication)功能,允许一台 Redis 服务器把自己的数据同步到一台或多台 Redis 服务器上。
在 Redis 旧术语中,通常把写入源头称为 Master ,把同步数据的一方称为 Slave。
在 Redis 新版本中,官方更推荐使用:
master/replicaREPLICAOF代替SLAVEOF
不过为了兼容老版本、老配置和大量资料,Redis 里仍然保留了 slaveof、slave 这些字段或命令。本文会以"主节点 / 从节点"描述概念,命令部分同时说明旧写法和新写法。
text
主节点 Master
↓ 数据单向同步
从节点 Replica 1
从节点 Replica 2
从节点 Replica 3
核心规则:
- 复制是单向的,数据只能从主节点同步到从节点。
- 主节点负责写入,从节点通常负责读取。
- 一个主节点可以有多个从节点。
- 一个从节点只能直接复制一个主节点。
- 从节点也可以继续作为其他从节点的主节点,形成树形复制结构。
主从复制解决的核心问题有两个:
第一,提升可用性。
如果只有一个 Redis 实例,一旦这个实例宕机,服务就不可用了。有了从节点之后,至少还存在数据副本,可以作为故障恢复和故障转移的基础。
第二,分担读压力。
写请求通常发送给主节点,读请求可以分发到多个从节点,从而降低主节点压力。
不过要注意:Redis 主从复制本身并不会自动把从节点提升为主节点。如果主节点宕机,普通主从复制需要人工处理;自动故障转移能力要依赖 Redis Sentinel 或 Redis Cluster。
三、建立和断开复制
3.1 建立复制的三种方式
建立主从关系的本质是:让从节点知道自己要复制哪一个主节点。
常见方式有三种。
方式一:配置文件(永久生效)
在从节点的配置文件中添加:
bash
slaveof <masterip> <masterport>
例如:
bash
slaveof 127.0.0.1 6379
Redis 新版本推荐写法是:
bash
replicaof <masterip> <masterport>
例如:
bash
replicaof 127.0.0.1 6379
配置文件方式会随 Redis 启动生效,重启之后仍然保持复制关系。
方式二:启动参数
启动 redis-server 时指定复制关系:
bash
redis-server redis-slave.conf --port 6380 --slaveof 127.0.0.1 6379
新版本也可以使用:
bash
redis-server redis-slave.conf --port 6380 --replicaof 127.0.0.1 6379
这种方式适合临时启动一个从节点实例。
方式三:命令行动态修改
在从节点执行:
bash
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
新版本推荐:
bash
127.0.0.1:6380> REPLICAOF 127.0.0.1 6379
OK
命令行方式会立即生效,但如果没有同步修改配置文件,实例重启后可能失效。
3.2 配置示例
假设默认启动的 Redis 使用 6379 端口,作为主节点。
复制一份配置文件作为从节点配置:
bash
cp redis.conf redis-slave.conf
修改从节点配置,使其后台运行:
bash
daemonize yes
然后启动从节点:
bash
redis-server redis-slave.conf --port 6380 --slaveof 127.0.0.1 6379
或者使用新命令:
bash
redis-server redis-slave.conf --port 6380 --replicaof 127.0.0.1 6379
注意:通常只需要修改从节点配置,主节点配置不需要改动。
可以通过端口检查两个 Redis 实例是否启动:
bash
netstat -nlpt
示例输出:
text
tcp 0 0 127.0.0.1:6379 0.0.0.0:* LISTEN redis-server
tcp 0 0 127.0.0.1:6380 0.0.0.0:* LISTEN redis-server
连接主节点写入数据:
bash
127.0.0.1:6379> SET hello world
OK
127.0.0.1:6379> GET hello
"world"
连接从节点读取数据:
bash
127.0.0.1:6380> GET hello
"world"
如果从节点可以读到主节点写入的数据,说明复制关系已经正常工作。
3.3 查看复制状态
可以通过 INFO replication 查看复制相关状态。
在主节点上执行:
bash
127.0.0.1:6379> INFO replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=100,lag=0
master_replid:2fbd35a8b8401b22eb92ff49ad5e42250b3e7a06
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:100
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:100
重点字段说明:
role:master:当前节点是主节点。connected_slaves:当前连接的从节点数量。slave0:从节点信息,包括 IP、端口、状态、复制偏移量和延迟。master_replid:当前复制历史的 replication id。master_replid2:第二复制 ID,主要用于故障切换后兼容旧复制历史。master_repl_offset:主节点当前复制偏移量。repl_backlog_size:复制积压缓冲区大小,默认 1MB。repl_backlog_first_byte_offset:复制积压缓冲区中最早数据的偏移量。repl_backlog_histlen:复制积压缓冲区中当前保存的数据长度。
在从节点上执行:
bash
127.0.0.1:6380> INFO replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:170
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:2fbd35a8b8401b22eb92ff49ad5e42250b3e7a06
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:170
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:170
重点字段说明:
role:slave:当前节点是从节点。master_host/master_port:当前复制的主节点地址和端口。master_link_status:与主节点连接是否正常。master_sync_in_progress:是否正在进行同步。slave_repl_offset:从节点当前复制到的偏移量。slave_read_only:从节点是否只读。master_replid:从节点记录的主节点复制 ID。master_repl_offset:当前复制流的偏移量。
3.4 断开复制
在从节点上执行:
bash
127.0.0.1:6380> SLAVEOF NO ONE
OK
新版本推荐:
bash
127.0.0.1:6380> REPLICAOF NO ONE
OK
断开复制后主要发生两件事:
- 从节点断开与主节点的复制关系。
- 从节点晋升为一个独立的主节点。
注意:断开复制不会删除从节点已有数据,只是从节点不再接收原主节点后续的数据变化。
3.5 切换主节点
也可以让从节点改为复制另一个主节点:
bash
127.0.0.1:6380> SLAVEOF 127.0.0.1 6381
OK
或者:
bash
127.0.0.1:6380> REPLICAOF 127.0.0.1 6381
OK
切主操作主要流程:
- 断开与旧主节点的复制关系。
- 与新主节点建立连接。
- 清空从节点当前数据。
- 从新主节点重新同步数据。
所以切主不是简单地"换一个连接",而是会以新主节点的数据为准重新建立复制。
3.6 安全性配置
如果主节点设置了密码:
bash
requirepass <password>
那么从节点需要配置:
bash
masterauth <password>
新版本也可以看到类似 masteruser、masterauth 这样的配置组合,用于 ACL 场景。
从节点连接主节点时,本质上也是一个特殊客户端。如果认证失败,从节点无法继续复制。
3.7 只读配置
从节点默认开启只读模式:
bash
slave-read-only yes
新版本配置名也可能写作:
bash
replica-read-only yes
建议线上环境保持只读。
原因很简单:Redis 复制是从主节点到从节点单向传播。如果直接修改从节点,主节点无法感知这次修改,后续主节点继续同步数据时,就可能造成数据不一致,甚至从节点本地修改被覆盖。
3.8 传输延迟控制
主从节点通常部署在不同机器上,网络延迟和带宽消耗都需要考虑。
Redis 提供了:
bash
repl-disable-tcp-nodelay no
这个配置用于控制是否关闭 TCP_NODELAY。
| 配置值 | 行为 | 适用场景 |
|---|---|---|
no |
不禁用 TCP_NODELAY,主节点会尽快发送命令 | 同机房、网络质量好、追求低延迟 |
yes |
禁用 TCP_NODELAY,合并小包后发送 | 跨机房、带宽紧张、允许更高延迟 |
简单理解:
no:延迟更低,但网络包更多,带宽消耗更大。yes:节省带宽,但可能增加几十毫秒级别的延迟。
四、主从拓扑结构
4.1 一主一从
一主一从是最简单的复制拓扑结构。
text
Redis-A Master ─────→ Redis-B Replica
这种结构常用于:
- 主节点出现故障时,从节点可以作为故障恢复的基础。
- 主节点承担写请求,从节点承担部分读请求。
- 从节点用于备份、持久化或数据分析。
一个常见优化是:
当主节点写压力比较大,又需要持久化时,可以只在从节点开启 AOF,主节点关闭 AOF。这样既能减少持久化对主节点性能的影响,又能在从节点保留较高的数据安全性。
但要特别注意:如果主节点关闭持久化,主节点宕机后不要让它自动重启并重新成为主节点。否则主节点可能以空数据集启动,然后把空数据同步给从节点,导致从节点数据也被清空。
4.2 一主多从(星形结构)
一主多从是读写分离最常见的结构。
text
┌──→ Redis-B Replica
Redis-A Master ────┼──→ Redis-C Replica
├──→ Redis-D Replica
└──→ Redis-E Replica
优点:
- 多个从节点可以分担读请求。
- 可以把耗时读命令放到专门的从节点执行。
- 可以为不同业务提供不同从库,例如报表查询、数据分析、备份节点。
缺点:
- 写请求仍然集中在主节点。
- 从节点越多,主节点需要发送的复制数据越多。
- 写并发较高时,主节点网络负载会明显增加。
- 从节点读到的数据可能存在延迟。
所以一主多从适合读多写少、读压力明显高于写压力的场景。
4.3 树形主从结构
树形结构也叫分层复制结构。从节点不仅可以复制主节点,还可以继续作为其他从节点的主节点。
text
Redis-A Master
├──→ Redis-B Replica
│ ├──→ Redis-D Replica
│ └──→ Redis-E Replica
│
└──→ Redis-C Replica
这种结构的优点是:可以降低顶层主节点的复制压力。
例如数据写入 Redis-A 后,A 只需要同步给 B 和 C。B 再继续同步给 D 和 E。这样 A 不需要直接维护大量从节点连接。
优点:
- 降低主节点网络负载。
- 适合从节点数量较多的场景。
- 可以按机房、地域、业务模块做分层复制。
缺点:
- 复制链路变长,延迟更高。
- 中间层从节点故障会影响下游节点。
- 数据一致性风险比一主多从更高。
树形结构适合对实时一致性要求不高、但副本数量比较多的场景。
五、复制的核心原理
5.1 建立复制的完整流程
从节点执行 SLAVEOF 或 REPLICAOF 后,复制不是瞬间完成的,而是经历多个阶段。
完整流程可以分为 6 步:
text
从节点 Replica 主节点 Master
│ │
│ 1. 保存主节点 IP 和端口 │
│ │
│ 2. 建立 TCP 连接 ───────────────────────────→ │
│ │
│ 3. PING ───────────────────────────────────→ │
│ ←────────────────────────────────── PONG │
│ │
│ 4. AUTH / ACL 认证 ────────────────────────→ │
│ ←────────────────────────────────── OK │
│ │
│ 5. PSYNC <replid> <offset> ────────────────→ │
│ ←──────── FULLRESYNC 或 CONTINUE │
│ │
│ 6. 持续命令传播 │
│ ←──────────────────────────── 写命令流 │
第一步:保存主节点信息
从节点执行复制命令后,会先保存主节点的 IP 和端口信息。
此时复制流程还没有真正开始。可以在从节点上看到类似状态:
bash
master_host:127.0.0.1
master_port:6379
master_link_status:down
master_link_status:down 表示当前还没有成功建立复制连接。
第二步:建立网络连接
从节点内部有定时任务维护复制逻辑。定时任务发现存在新的主节点配置后,会尝试和主节点建立 TCP 连接。
如果连接失败,从节点会不断重试,直到连接成功,或者用户执行 SLAVEOF NO ONE / REPLICAOF NO ONE 取消复制关系。
第三步:发送 PING 命令
TCP 连接建立后,从节点会向主节点发送 PING。
这一步的作用是:
- 检查网络连接是否可用。
- 检查主节点是否可以正常处理命令。
如果 PING 超时或主节点返回异常,从节点会断开连接,等待下一轮重试。
第四步:权限验证
如果主节点设置了 requirepass 或 ACL 认证,从节点需要通过 masterauth 等配置完成认证。
认证失败后,复制流程无法继续。
第五步:同步数据集
认证通过后,从节点开始和主节点同步数据。
Redis 使用 PSYNC 命令完成同步协商。主节点根据从节点传来的 replid 和 offset 判断:
- 是否可以部分复制;
- 是否必须全量复制。
第六步:持续命令传播
同步完成后,主节点会把后续写命令持续发送给从节点。从节点执行这些写命令,从而保持和主节点数据一致。
这个阶段也叫实时复制或命令传播。
5.2 runid、replid、offset 的区别
这一节非常关键,因为很多资料会把 runid 和 replid 混在一起讲,导致理解错误。
1)runid:实例本次运行的唯一标识
runid 是 Redis 实例每次启动时生成的唯一运行标识。
它描述的是:
text
这个 Redis 进程的本次运行身份
同一个 Redis 实例,只要重启,runid 就会变化。
可以通过 INFO server 查看:
bash
127.0.0.1:6379> INFO server
# Server
redis_version:7.x.x
redis_mode:standalone
process_id:12345
run_id:9f0b7c8d6e5a4b3c2d1e...
runid 的典型用途是识别 Redis 实例本身,尤其是在 Sentinel 这类高可用监控场景中。Sentinel 会记录和传播 Redis 实例或 Sentinel 实例的运行 ID,用来区分"同一个地址上的旧进程"和"重启后的新进程"。
所以:
text
runid = 标识 Redis 实例本次运行
它不是当前 Redis 复制同步判断的核心参数。
2)replid:复制历史的唯一标识
replid 是 replication id,也就是复制 ID。
它描述的是:
text
当前这条复制数据历史
在 INFO replication 中可以看到:
bash
master_replid:1da596acecf5a34b4b2aae45bd35be785691ae69
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
当前 Redis 的 PSYNC 同步协商使用的是:
bash
PSYNC <replicationid> <offset>
也就是说,部分复制能否成功,主要看:
text
replid 是否能匹配
offset 是否还在复制积压缓冲区范围内
而不是看 runid。
3)offset:复制偏移量
offset 是复制偏移量,可以理解为复制进度。
主节点每处理并传播一段写命令数据,就会累加自己的复制偏移量。
从节点接收到主节点发送的数据后,也会累加自己的复制偏移量。
可以在主节点看到:
bash
master_repl_offset:1055130
也可以在从节点看到:
bash
slave_repl_offset:1055214
还可以在主节点看到每个从节点的复制进度:
bash
slave0:ip=127.0.0.1,port=6380,state=online,offset=1055214,lag=1
可以用课堂例子理解:
老师准备了 100 页课件,助教学到了第 80 页。那么 offset 就类似"助教当前学到哪里了"。
4)replid + offset 共同标识一份复制数据历史中的位置
replid 说明"是哪一条数据历史",offset 说明"这条历史同步到了哪里"。
所以:
text
replid + offset ≈ 某一份复制数据集的同步位置
如果两个节点处于同一个 replid,并且 offset 也相同,就可以认为它们在这条复制历史上的数据进度一致。
5.3 master_replid 和 master_replid2
Redis 为什么要有两个复制 ID?
bash
master_replid
master_replid2
原因和故障切换有关。
假设当前有两个节点:
text
A:Master
B:Replica
B 复制 A,所以 B 记录了 A 的 master_replid。
如果 A 故障,B 被提升为新的主节点。此时 B 需要开启一条新的复制历史,所以 B 会生成新的 master_replid。
但是问题来了:
如果还有其他从节点之前复制的是 A,它们手里保存的是 A 的旧 replid 和 offset。现在 B 成了新主节点,如果 B 只认自己的新 master_replid,这些从节点就都无法部分复制,只能全量复制。
为了解决这个问题,Redis 引入了 master_replid2。
当 B 晋升为主节点时:
- B 会生成新的
master_replid,表示新的复制历史。 - B 会把旧主节点 A 的复制 ID 保存到
master_replid2。 - B 同时记录
second_repl_offset,表示旧复制 ID 仍然有效到哪个偏移量。
这样,其他从节点来找 B 做 PSYNC 时,即使带的是旧主节点 A 的 replid,只要 offset 仍在有效范围内,B 仍然可以接受部分复制。
简单说:
text
master_replid = 当前复制历史 ID
master_replid2 = 上一段复制历史 ID,主要用于故障切换后的部分复制兼容
这个设计减少了故障切换后大量从节点全量复制的概率。
5.4 psync 机制
Redis 使用 PSYNC 命令完成主从数据同步协商。
语法格式:
bash
PSYNC <replicationid> <offset>
如果是首次复制,从节点不知道主节点的复制 ID,也没有复制偏移量,就会发送:
bash
PSYNC ? -1
这表示:
text
我不知道之前复制的是谁,也没有历史进度,请求全量复制。
如果是断线重连,从节点会带上之前保存的复制 ID 和偏移量:
bash
PSYNC 2fbd35a8b8401b22eb92ff49ad5e42250b3e7a06 1055130
这表示:
text
我之前复制的是这个 replid,并且已经同步到 offset=1055130,请问能不能从这里继续补数据?
主节点收到 PSYNC 后,可能返回三种结果。
1)+FULLRESYNC
bash
+FULLRESYNC <replid> <offset>
表示需要全量复制。
常见原因:
- 从节点第一次复制,发送的是
PSYNC ? -1。 - 从节点传来的
replid主节点不认识。 - 从节点传来的 offset 太旧,对应数据已经不在复制积压缓冲区里。
- 主节点无法提供所需的部分数据。
2)+CONTINUE
bash
+CONTINUE
或者新版本中可能带上复制 ID:
bash
+CONTINUE <replid>
表示可以部分复制。
主节点会从复制积压缓冲区中找到从节点缺失的数据,并补发给从节点。
3)-ERR
bash
-ERR
表示主节点不支持 PSYNC,通常是非常老的 Redis 版本。
这时从节点可能退化为使用 SYNC 命令进行全量复制。
不过现在实际工作中基本都是 PSYNC。
5.5 全量复制、部分复制、实时复制
Redis 复制可以分成三类:
| 类型 | 作用 | 典型场景 |
|---|---|---|
| 全量复制 | 复制完整数据集 | 首次复制、无法部分复制 |
| 部分复制 | 补发断线期间缺失的数据 | 网络闪断后重连 |
| 实时复制 | 持续传播后续写命令 | 主从正常连接期间 |
六、全量复制
6.1 什么时候触发全量复制
全量复制通常发生在以下场景:
- 从节点第一次连接主节点。
- 从节点发送
PSYNC ? -1。 - 主节点不认识从节点带来的
replid。 - 从节点需要的 offset 已经超出复制积压缓冲区范围。
- 从节点切换到新的主节点。
- 某些异常场景下部分复制无法继续。
全量复制的特点是:主节点需要把当前完整数据集发送给从节点。
如果 Redis 数据量很大,全量复制成本会非常高。
6.2 全量复制的完整流程
text
从节点 Replica 主节点 Master
│ │
│ 1. PSYNC ? -1 ─────────────────────────────→ │
│ │
│ ←──────── +FULLRESYNC <replid> <offset> │
│ │
│ 2. 保存主节点 replid 和 offset │
│ │
│ 3. 主节点执行 bgsave 生成 RDB │
│ │
│ 4. RDB 生成期间,主节点继续处理写命令 │
│ 写命令进入复制客户端缓冲区 │
│ │
│ 5. 主节点发送 RDB 文件 ─────────────────────→ │
│ │
│ 6. 从节点接收并保存 RDB │
│ │
│ 7. 从节点清空旧数据 │
│ │
│ 8. 从节点加载 RDB │
│ │
│ 9. 主节点发送 RDB 期间积累的增量写命令 ─────→ │
│ │
│ 10. 从节点执行增量写命令 │
│ │
│ 11. 如果从节点开启 AOF,后续可能触发 AOF 重写 │
详细说明如下。
第一步:从节点发送 PSYNC
首次复制时,从节点没有主节点的复制 ID,也没有复制偏移量,所以发送:
bash
PSYNC ? -1
第二步:主节点返回 FULLRESYNC
主节点判断这是首次复制,于是返回:
bash
+FULLRESYNC <replid> <offset>
这里返回的是主节点当前的 replid 和复制偏移量,而不是 runid。
从节点会保存这两个信息,用于后续断线重连时尝试部分复制。
第三步:主节点生成 RDB
主节点执行 bgsave,在后台生成 RDB 快照。
因为是后台生成,所以主节点仍然可以继续处理客户端请求。
第四步:主节点缓存增量写命令
在 RDB 生成和传输期间,主节点可能继续接收写命令。
这些写命令不能丢,否则从节点加载完 RDB 后就会落后于主节点。
所以主节点会把这段时间产生的写命令缓存起来,等从节点加载 RDB 后再发送给从节点。
这里要区分两个缓冲区:
- 复制积压缓冲区:用于断线重连后的部分复制。
- 复制客户端缓冲区:用于某个从节点全量复制期间缓存增量写命令。
这两个不是同一个东西。
第五步:主节点发送 RDB
主节点把 RDB 数据发送给从节点。
默认情况下,主节点会先把 RDB 写到磁盘,再从磁盘读取并发送给从节点。
第六步:从节点接收 RDB
从节点接收 RDB 数据,并保存到本地。
第七步:从节点清空旧数据
从节点在加载主节点 RDB 前,会清空自身旧数据。
这是因为复制要以主节点数据为准。
第八步:从节点加载 RDB
从节点加载 RDB 文件,得到和主节点生成 RDB 那一刻一致的数据。
加载 RDB 期间,从节点通常无法正常处理客户端请求。
第九步:主节点发送增量写命令
RDB 加载完成后,主节点把 RDB 生成和传输期间缓存的增量写命令发送给从节点。
从节点执行这些命令后,数据就追上主节点。
第十步:进入实时复制
全量同步完成后,主节点继续把后续写命令实时发送给从节点。
6.3 全量复制的成本
全量复制是一个高成本操作,成本主要来自:
- 主节点执行
bgsave的 CPU 和内存开销。 - RDB 文件写磁盘的开销。
- RDB 文件网络传输开销。
- 从节点清空旧数据的开销。
- 从节点加载 RDB 的开销。
- RDB 生成和传输期间增量写命令的缓冲区开销。
如果数据量很大,频繁全量复制可能导致 Redis 性能明显下降,甚至引发主从复制反复失败。
例如:
- 主节点生成很大的 RDB。
- 网络传输很慢。
- 传输期间主节点写入量很高。
- 复制客户端缓冲区被撑爆。
- 主节点断开从节点连接。
- 从节点重新连接,又触发全量复制。
- 进入全量复制死循环。
这类问题通常需要从几个方向优化:
- 增大复制客户端缓冲区限制。
- 增大复制积压缓冲区。
- 降低主节点写入峰值。
- 优化网络带宽。
- 避免一次性挂载大量从节点。
- 使用无盘复制减少磁盘压力。
6.4 有盘复制与无盘复制
Redis 全量复制有两种方式:
- 有盘复制。
- 无盘复制。
有盘复制
默认情况下,主节点执行 bgsave,把 RDB 文件写入磁盘,然后再把 RDB 文件发送给从节点。
流程如下:
text
内存数据 → RDB 文件落盘 → 读取 RDB 文件 → 网络发送给从节点
优点是实现简单,稳定性好。
缺点是磁盘 IO 开销比较大。
无盘复制
Redis 支持无盘复制。无盘复制中,主节点生成 RDB 时不先落盘,而是直接通过网络发送给从节点。
流程如下:
text
内存数据 → 生成 RDB 数据流 → 直接网络发送给从节点
优点是减少磁盘读写开销,适合磁盘性能较差或磁盘压力较大的场景。
相关配置示例:
bash
repl-diskless-sync yes
还可以配置等待时间,用于让多个从节点尽量一起接收同一轮 RDB 传输:
bash
repl-diskless-sync-delay 5
七、部分复制
7.1 为什么需要部分复制
全量复制代价很高。
如果只是网络抖动导致从节点断开几秒钟,重新连接后就全量复制,显然非常浪费。
部分复制就是为了解决这个问题。
它允许从节点断线重连后,只补齐断线期间缺失的写命令,而不是重新复制整个数据集。
7.2 部分复制的触发条件
部分复制成功需要满足两个核心条件:
第一,replid 能匹配。
从节点发送的 replid 必须是主节点当前认识的复制历史。
它可以匹配:
- 主节点当前的
master_replid。 - 主节点保留的
master_replid2。
第二,offset 仍在复制积压缓冲区范围内。
从节点需要的数据还没有被复制积压缓冲区覆盖。
如果从节点断线太久,主节点写入太多,旧数据被覆盖,就无法部分复制,只能全量复制。
7.3 部分复制的完整流程
text
从节点 Replica 主节点 Master
│ │
│ 1. 网络中断 │
│ │
│ │ 2. 主节点继续处理写命令
│ │ 写命令写入复制积压缓冲区
│ │
│ 3. 网络恢复,重新连接主节点 │
│ │
│ 4. PSYNC <replid> <offset> ────────────────→ │
│ │
│ 5. 主节点检查 replid 和 offset │
│ │
│ 如果满足部分复制条件: │
│ ←──────────────────────────── +CONTINUE │
│ │
│ 6. 主节点补发 offset 之后的数据 ────────────→ │
│ │
│ 7. 从节点执行缺失命令,恢复一致 │
详细过程如下:
- 主从节点之间网络中断。
- 如果超过
repl-timeout,主节点认为从节点故障,断开复制连接。 - 网络中断期间,主节点仍然处理写命令。
- 主节点把写命令写入复制积压缓冲区。
- 从节点重新连上主节点。
- 从节点发送之前保存的
replid和offset。 - 主节点判断是否能进行部分复制。
- 如果可以,返回
+CONTINUE。 - 主节点从复制积压缓冲区中取出缺失数据,发送给从节点。
- 从节点执行缺失命令,主从恢复一致。
用课堂例子理解:
老师讲课时,助教网络断了,少听了第 81 页到第 90 页。网络恢复后,助教不需要从第 1 页重新学,只需要告诉老师:"我学到第 80 页了,请把第 81 页之后的内容补给我。"
这里:
replid表示是哪一套课件。offset表示学到了哪一页。- 复制积压缓冲区表示老师最近保留的课件记录。
7.4 复制积压缓冲区
复制积压缓冲区(replication backlog)是主节点维护的一个固定大小的环形缓冲区,默认大小是 1MB。
配置项:
bash
repl-backlog-size 1mb
当主节点有从节点连接时,主节点会创建复制积压缓冲区。
主节点处理写命令时,会做两件事:
- 把写命令发送给从节点。
- 把写命令写入复制积压缓冲区。
结构可以理解为:
text
复制积压缓冲区(环形队列)
offset: 1000 1001 1002 1003 1004 1005
data: SET HSET DEL LPUSH SET INCR
如果从节点断线前同步到 offset=1002,重连后只要 1003 之后的数据还在缓冲区里,主节点就可以补发 1003 之后的数据。
如果从节点断线太久,缓冲区已经被新数据覆盖,那么 offset=1003 对应的数据找不到了,就只能全量复制。
7.5 复制积压缓冲区范围计算
可以通过 INFO replication 查看复制积压缓冲区状态:
bash
127.0.0.1:6379> INFO replication
# Replication
role:master
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:7479
repl_backlog_histlen:1048576
字段说明:
repl_backlog_active:复制积压缓冲区是否启用。repl_backlog_size:缓冲区最大长度。repl_backlog_first_byte_offset:缓冲区中最早数据的偏移量。repl_backlog_histlen:缓冲区当前保存的有效数据长度。
可用偏移量范围大致是:
text
[repl_backlog_first_byte_offset,
repl_backlog_first_byte_offset + repl_backlog_histlen]
如果从节点请求的 offset 在这个范围内,就有机会进行部分复制。
如果不在这个范围内,就必须全量复制。
7.6 如何设置 repl-backlog-size
默认 1MB 在写入量较小的场景下可能够用,但在写入量较大的生产环境中可能太小。
可以根据公式粗略估算:
text
repl-backlog-size >= 主节点每秒写入复制流量 × 允许从节点断线的秒数
例如:
- 主节点每秒产生 2MB 复制数据。
- 希望从节点断线 60 秒内都能部分复制。
那么:
text
2MB/s × 60s = 120MB
可以设置:
bash
repl-backlog-size 128mb
这不是固定公式,只是估算思路。实际还需要结合内存、写入峰值、网络情况和从节点数量调整。
八、实时复制(命令传播)
8.1 命令传播
全量复制或部分复制完成后,主从之间进入实时复制阶段。
主节点每执行一个会修改数据的命令,都会把该命令发送给从节点。
例如主节点执行:
bash
SET name redis
INCR counter
HSET user:1 name zhangsan
这些写命令会通过主从之间的长连接发送给从节点。从节点收到后执行相同命令,从而保持数据一致。
复制连接本质上是 TCP 长连接。
主节点不断把写命令推送给从节点,从节点不断执行这些命令。
8.2 心跳机制
主从之间需要维护连接状态,所以 Redis 在应用层实现了心跳机制。
注意:这里说的是 Redis 自己的应用层心跳,不是 TCP 自带的 keepalive。
主从双方都有心跳行为。
主节点向从节点发送 PING
主节点默认每隔 10 秒向从节点发送一次 PING。
配置项:
bash
repl-ping-slave-period 10
新版本中配置名称可能仍保留 slave 字样,这是为了兼容旧配置。
作用:
- 判断从节点是否存活。
- 维持复制连接。
- 检测网络异常。
从节点向主节点发送 REPLCONF ACK
从节点默认每秒向主节点发送:
bash
REPLCONF ACK <offset>
例如:
bash
REPLCONF ACK 1055214
这条命令表示:
text
我当前已经复制到 offset=1055214。
主节点收到 ACK 后,可以知道每个从节点的复制进度。
这有几个作用:
- 判断主从延迟。
- 统计从节点 lag。
- 辅助
min-replicas-to-write等配置控制写入安全性。 - 帮助主节点了解从节点是否还活着。
8.3 repl-timeout
如果主从之间超过一定时间没有通信,Redis 会认为复制连接异常。
配置项:
bash
repl-timeout 60
默认通常是 60 秒。
如果主节点发现从节点超过 repl-timeout 没有正常通信,就会断开复制连接。
从节点发现连接断开后,会重新连接主节点,并通过 PSYNC <replid> <offset> 尝试恢复复制。
如果条件满足,走部分复制。
如果条件不满足,走全量复制。
8.4 最小从节点写入保护
Redis 还提供了一类配置,可以要求主节点在满足一定从节点同步条件时才继续接受写入。
旧配置名:
bash
min-slaves-to-write 1
min-slaves-max-lag 10
新配置名:
bash
min-replicas-to-write 1
min-replicas-max-lag 10
含义是:
text
至少有 1 个从节点,并且延迟不超过 10 秒,主节点才接受写入。
如果条件不满足,主节点会拒绝写命令。
这不是强一致机制,但可以降低主节点孤立情况下继续写入导致数据丢失的风险。
九、总结
现在你已经掌握了 Redis 主从复制的核心内容。
9.1 主从复制解决的问题
主从复制主要解决两个问题:
第一,单点可用性问题。
单个 Redis 节点宕机后服务不可用。有了从节点后,可以为故障恢复、哨兵故障转移、集群高可用提供基础。
第二,单节点读性能问题。
单个 Redis 节点承载能力有限。通过一主多从,可以把读请求分散到多个从节点,降低主节点压力。
9.2 主从复制的特点
Redis 主从复制有以下特点:
- Redis 通过复制功能实现主节点数据的多个副本。
- 复制数据流是单向的,从主节点流向从节点。
- 主节点通常负责写,从节点通常负责读。
- 一个主节点可以有多个从节点。
- 一个从节点只能直接复制一个主节点。
- 从节点也可以作为其他从节点的主节点,形成树形结构。
- 复制分为全量复制、部分复制和实时复制。
- 主从之间通过心跳机制维护连接状态。
- 普通主从复制不会自动故障转移,自动故障转移需要 Sentinel 或 Cluster。
9.3 runid、replid、offset 重点回顾
这几个概念一定要区分清楚。
| 概念 | 含义 | 主要用途 |
|---|---|---|
runid |
Redis 实例本次运行的唯一标识 | 标识进程本次运行,Sentinel 等场景会用到 |
replid |
复制历史 ID | PSYNC 判断是否属于同一条复制历史 |
offset |
复制偏移量 | 表示复制进度 |
master_replid |
当前复制历史 ID | 当前主节点复制流 |
master_replid2 |
第二复制历史 ID | 故障切换后兼容旧复制历史 |
second_repl_offset |
第二复制 ID 有效到的偏移量 | 限定 master_replid2 的有效范围 |
最重要的一句话:
text
当前 Redis 主从复制的 PSYNC 判断依据是 replid + offset,不是 runid。
旧资料中把 runid 写进 PSYNC 参数,多数是沿用了 Redis 早期 PSYNC 设计或旧文章说法。现在结合 Redis 官方文档、INFO replication 字段和源码实现,应该统一写成:
bash
PSYNC <replicationid> <offset>
全量复制响应也应该写成:
bash
+FULLRESYNC <replid> <offset>
而不是:
bash
+FULLRESYNC <runid> <offset>
9.4 全量复制 vs 部分复制
| 对比维度 | 全量复制 | 部分复制 |
|---|---|---|
| 触发条件 | 首次复制、replid 不匹配、offset 超出缓冲区 | replid 匹配且 offset 在缓冲区范围内 |
| 传输内容 | 完整 RDB + 增量写命令 | 缺失的增量写命令 |
| 成本 | 高 | 低 |
| 对主节点影响 | bgsave、网络传输、缓冲区压力 | 较小 |
| 对从节点影响 | 清空旧数据、加载 RDB | 只执行缺失命令 |
| 适用场景 | 初始同步、无法部分复制 | 网络短暂闪断后恢复 |
9.5 常见配置速查
bash
# 建立复制关系,旧写法
slaveof <masterip> <masterport>
# 建立复制关系,新写法
replicaof <masterip> <masterport>
# 断开复制关系,旧写法
slaveof no one
# 断开复制关系,新写法
replicaof no one
# 从节点只读,旧配置名
slave-read-only yes
# 从节点只读,新配置名
replica-read-only yes
# 主节点密码认证
requirepass <password>
# 从节点连接主节点的认证密码
masterauth <password>
# 复制积压缓冲区大小
repl-backlog-size 1mb
# 主节点向从节点发送 PING 的间隔
repl-ping-slave-period 10
# 主从复制超时时间
repl-timeout 60
# 是否禁用 TCP_NODELAY
repl-disable-tcp-nodelay no
# 无盘复制
repl-diskless-sync yes
# 最小从节点写入保护,旧配置名
min-slaves-to-write 1
min-slaves-max-lag 10
# 最小从节点写入保护,新配置名
min-replicas-to-write 1
min-replicas-max-lag 10
9.6 主从复制的不足
主从复制虽然重要,但它本身并不等于高可用。
主要不足有:
-
主节点故障后,从节点不会自动升级为主节点。
普通主从复制需要人工执行切换。自动故障转移需要 Sentinel 或 Cluster。
-
从节点越多,主节点复制压力越大。
主节点需要把写命令发送给每个从节点,从节点数量越多,网络开销越大。
-
复制是异步的,存在数据延迟。
主节点写入成功后,从节点可能还没同步完成。如果主节点此时故障,可能存在数据丢失。
-
全量复制成本高。
大数据量场景下,全量复制会带来明显的 CPU、内存、磁盘和网络开销。
-
链式复制延迟更明显。
树形主从结构可以降低主节点压力,但会增加数据传播延迟。
下一篇预告:Redis 哨兵(Sentinel)------ 主从复制的致命缺陷是什么、哨兵如何自动故障转移、选举机制和工作原理。