【Redis篇】Redis 主从复制:数据同步的原理与实现

文章目录

    • [Redis 主从复制:数据同步的原理与实现](#Redis 主从复制:数据同步的原理与实现)
    • 一、前言
    • 二、主从复制是什么
      • [2.1 基本概念](#2.1 基本概念)
    • 三、建立和断开复制
    • 四、主从拓扑结构
      • [4.1 一主一从](#4.1 一主一从)
      • [4.2 一主多从(星形结构)](#4.2 一主多从(星形结构))
      • [4.3 树形主从结构](#4.3 树形主从结构)
    • 五、复制的核心原理
    • 六、全量复制
      • [6.1 什么时候触发全量复制](#6.1 什么时候触发全量复制)
      • [6.2 全量复制的完整流程](#6.2 全量复制的完整流程)
      • [6.3 全量复制的成本](#6.3 全量复制的成本)
      • [6.4 有盘复制与无盘复制](#6.4 有盘复制与无盘复制)
    • 七、部分复制
      • [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 的主从复制机制

🚀 核心内容

  • 如何建立和断开主从复制关系?
  • 三种主从拓扑结构各有什么特点?
  • runidreplidoffset 分别是什么?
  • psync 机制是什么?如何决定全量复制还是部分复制?
  • 全量复制、部分复制、实时复制的完整流程是怎样的?

上一篇讲完了 Redis 事务,这一篇进入 Redis 高可用体系的第一块基石 ------ 主从复制。

在分布式系统中,为了解决单点问题,通常会把数据复制多个副本部署到其他服务器上,从而满足故障恢复、读写分离、负载均衡等需求。Redis 也提供了复制功能,可以实现同一份数据在多个 Redis 实例之间同步。

复制功能是 Redis 高可用体系的基础。后面的哨兵(Sentinel)和集群(Cluster)都建立在复制能力之上。所以这一篇的重点不是死记命令,而是理解复制的流程和原理。

可以用一个课堂里的例子理解:

如果学生比较少,一个老师既可以授课,也可以答疑;但是随着学生越来越多,一个老师就应付不过来了。这时就需要配几个助教老师。助教老师从授课老师这里获取知识,再协助授课老师答疑。

Redis 主从复制也是类似的:

  • 主节点就像授课老师,负责写入和产生最新数据。
  • 从节点就像助教老师,从主节点同步数据,并承担读请求。
  • 如果主节点出问题,从节点还可以作为后续故障切换的基础。

二、主从复制是什么

2.1 基本概念

Redis 提供了复制(Replication)功能,允许一台 Redis 服务器把自己的数据同步到一台或多台 Redis 服务器上。

在 Redis 旧术语中,通常把写入源头称为 Master ,把同步数据的一方称为 Slave

在 Redis 新版本中,官方更推荐使用:

  • master / replica
  • REPLICAOF 代替 SLAVEOF

不过为了兼容老版本、老配置和大量资料,Redis 里仍然保留了 slaveofslave 这些字段或命令。本文会以"主节点 / 从节点"描述概念,命令部分同时说明旧写法和新写法。

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

断开复制后主要发生两件事:

  1. 从节点断开与主节点的复制关系。
  2. 从节点晋升为一个独立的主节点。

注意:断开复制不会删除从节点已有数据,只是从节点不再接收原主节点后续的数据变化。

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

切主操作主要流程:

  1. 断开与旧主节点的复制关系。
  2. 与新主节点建立连接。
  3. 清空从节点当前数据。
  4. 从新主节点重新同步数据。

所以切主不是简单地"换一个连接",而是会以新主节点的数据为准重新建立复制。

3.6 安全性配置

如果主节点设置了密码:

bash 复制代码
requirepass <password>

那么从节点需要配置:

bash 复制代码
masterauth <password>

新版本也可以看到类似 masterusermasterauth 这样的配置组合,用于 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 建立复制的完整流程

从节点执行 SLAVEOFREPLICAOF 后,复制不是瞬间完成的,而是经历多个阶段。

完整流程可以分为 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 命令完成同步协商。主节点根据从节点传来的 replidoffset 判断:

  • 是否可以部分复制;
  • 是否必须全量复制。

第六步:持续命令传播

同步完成后,主节点会把后续写命令持续发送给从节点。从节点执行这些写命令,从而保持和主节点数据一致。

这个阶段也叫实时复制或命令传播。

5.2 runid、replid、offset 的区别

这一节非常关键,因为很多资料会把 runidreplid 混在一起讲,导致理解错误。

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 性能明显下降,甚至引发主从复制反复失败。

例如:

  1. 主节点生成很大的 RDB。
  2. 网络传输很慢。
  3. 传输期间主节点写入量很高。
  4. 复制客户端缓冲区被撑爆。
  5. 主节点断开从节点连接。
  6. 从节点重新连接,又触发全量复制。
  7. 进入全量复制死循环。

这类问题通常需要从几个方向优化:

  • 增大复制客户端缓冲区限制。
  • 增大复制积压缓冲区。
  • 降低主节点写入峰值。
  • 优化网络带宽。
  • 避免一次性挂载大量从节点。
  • 使用无盘复制减少磁盘压力。

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. 从节点执行缺失命令,恢复一致                  │

详细过程如下:

  1. 主从节点之间网络中断。
  2. 如果超过 repl-timeout,主节点认为从节点故障,断开复制连接。
  3. 网络中断期间,主节点仍然处理写命令。
  4. 主节点把写命令写入复制积压缓冲区。
  5. 从节点重新连上主节点。
  6. 从节点发送之前保存的 replidoffset
  7. 主节点判断是否能进行部分复制。
  8. 如果可以,返回 +CONTINUE
  9. 主节点从复制积压缓冲区中取出缺失数据,发送给从节点。
  10. 从节点执行缺失命令,主从恢复一致。

用课堂例子理解:

老师讲课时,助教网络断了,少听了第 81 页到第 90 页。网络恢复后,助教不需要从第 1 页重新学,只需要告诉老师:"我学到第 80 页了,请把第 81 页之后的内容补给我。"

这里:

  • replid 表示是哪一套课件。
  • offset 表示学到了哪一页。
  • 复制积压缓冲区表示老师最近保留的课件记录。

7.4 复制积压缓冲区

复制积压缓冲区(replication backlog)是主节点维护的一个固定大小的环形缓冲区,默认大小是 1MB。

配置项:

bash 复制代码
repl-backlog-size 1mb

当主节点有从节点连接时,主节点会创建复制积压缓冲区。

主节点处理写命令时,会做两件事:

  1. 把写命令发送给从节点。
  2. 把写命令写入复制积压缓冲区。

结构可以理解为:

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 主从复制有以下特点:

  1. Redis 通过复制功能实现主节点数据的多个副本。
  2. 复制数据流是单向的,从主节点流向从节点。
  3. 主节点通常负责写,从节点通常负责读。
  4. 一个主节点可以有多个从节点。
  5. 一个从节点只能直接复制一个主节点。
  6. 从节点也可以作为其他从节点的主节点,形成树形结构。
  7. 复制分为全量复制、部分复制和实时复制。
  8. 主从之间通过心跳机制维护连接状态。
  9. 普通主从复制不会自动故障转移,自动故障转移需要 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 主从复制的不足

主从复制虽然重要,但它本身并不等于高可用。

主要不足有:

  1. 主节点故障后,从节点不会自动升级为主节点。

    普通主从复制需要人工执行切换。自动故障转移需要 Sentinel 或 Cluster。

  2. 从节点越多,主节点复制压力越大。

    主节点需要把写命令发送给每个从节点,从节点数量越多,网络开销越大。

  3. 复制是异步的,存在数据延迟。

    主节点写入成功后,从节点可能还没同步完成。如果主节点此时故障,可能存在数据丢失。

  4. 全量复制成本高。

    大数据量场景下,全量复制会带来明显的 CPU、内存、磁盘和网络开销。

  5. 链式复制延迟更明显。

    树形主从结构可以降低主节点压力,但会增加数据传播延迟。

下一篇预告:Redis 哨兵(Sentinel)------ 主从复制的致命缺陷是什么、哨兵如何自动故障转移、选举机制和工作原理。

相关推荐
真实的菜1 小时前
Redis 从入门到精通(五):哨兵模式(Sentinel)—— 自动故障转移的完整原理与实战
数据库·redis·sentinel
Solis程序员1 小时前
缓存三剑客预防策略
java·spring·缓存
是小王同学啊~1 小时前
Redis 面试通关笔记:高频八股 + 生产实战 + 追问链路(下)
redis·面试题
唔661 小时前
(二)补充完整的数据库、中间件、MQTT、JAR后台和Web前端的部署脚本,全部一键自动化。
数据库·中间件·jar
六月雨滴1 小时前
Oracle 内存优化
数据库·oracle
学代码的真由酱1 小时前
MySQL数据库进阶-数据库设计实践-Java
数据库·mysql·数据库设计
遇事不決洛必達2 小时前
【数据库系列】本地映射云服务器Mysql的方法
服务器·数据库·mysql·定时任务
海鸥-w2 小时前
用python (fastapi)做项目第一天创建项目结构,数据建表,ORM配置安装,写第一个接口
数据库·python·fastapi
zfoo-framework2 小时前
通过redis-cli+lua脚本查询redis数据
数据库·redis·lua