Skip to content

主从复制 ​

单机 Redis 存在单点故障与读写瓶颈。主从复制让数据在多个节点间同步,是哨兵与集群的基石。本章从同步流程到源码级细节,讲清主从复制的全貌,以及生产环境中最常见的坑。

1. 为什么需要主从复制 ​

单机 Redis 面临三个问题:

问题说明
单点故障主节点宕机,服务不可用
读瓶颈所有请求都压在一个节点上
容量受限单机内存有上限

主从复制引入「一主多从」结构:主节点负责写,从节点复制数据并分担读请求。

2. 全量同步 ​

首次连接或增量同步失败时,从节点需要复制主节点的全部数据。

2.1 流程 ​

全量同步的时序:

2.2 BGSAVE 的 fork 代价 ​

BGSAVE 通过 fork() 创建子进程,利用操作系统的 Copy-On-Write(COW) 机制生成 RDB。fork 的代价:

项目说明
fork 耗时取决于内存页表大小,具体值用 latest_fork_usec 实测(见 持久化)
内存开销fork 后父子进程共享内存页,写入时才复制,极端情况内存翻倍
主线程阻塞fork 期间主线程阻塞;实际耗时取决于页表规模和系统负载

生产环境需要按 latest_fork_usec、数据规模和业务延迟预算评估单实例大小。数据量只是影响因素之一,不能用固定的 10GB 阈值代替实测;同时应避免多个从节点无控制地同时触发全量同步。

2.3 复制缓冲区大小 ​

复制积压缓冲区(repl_backlog)是一个环形缓冲区,保存最近的复制流。它决定从节点断线多久后仍可通过 partial resync 补齐数据;覆盖窗口不足时,从节点重连后需要重新全量同步。全量同步期间还要结合副本输出缓冲区、网络和磁盘能力评估容量。

bash
# 查看缓冲区大小
redis-cli INFO replication | grep repl_backlog_size

# 调整大小(默认 1MB,生产环境建议调大)
# redis.conf
repl-backlog-size 256mb

缓冲区大小应按可接受的断线窗口和峰值写入速率计算:

txt
repl-backlog-size ≥ 可接受断线时长(s)× 峰值复制写入速率(B/s)× 余量系数

下表仅演示公式,不是通用推荐值。实际配置应代入峰值写入速率、网络抖动窗口、重连时间和内存预算:

场景峰值写入速率可接受断线时长估算值(含 1.5 倍余量)
低写入1 MB/s300s约 450 MB
中等写入10 MB/s300s约 4.5 GB
高写入50 MB/s600s约 45 GB

3. 增量同步 ​

断线重连后,如果从节点的偏移量还在主节点的复制缓冲区范围内,就只补发缺失部分。

3.1 PSYNC 机制 ​

txt
从节点发送:PSYNC <runid> <offset>
主节点检查:
  - runid 匹配 && offset 在缓冲区内 → +CONTINUE <offset>,增量同步
  - runid 不匹配 || offset 不在缓冲区 → +FULLRESYNC,全量同步

3.2 偏移量与积压缓冲区 ​

概念说明
复制偏移量(replication offset)主从各自维护,记录已复制的字节数
积压缓冲区(repl_backlog)主节点的环形缓冲区,保存最近的写命令
命令传播全量同步后,主节点持续把写命令传播给从节点

主节点每次写入时,同时写入积压缓冲区并推进偏移量。从节点每收到一批命令,也推进自己的偏移量。两者之差表示尚未复制的字节数;要换算成时间延迟,还需要结合当前复制速率。

bash
# 查看主从偏移量差
redis-cli INFO replication
# master_repl_offset: 123456789
# slave_repl_offset:  123456000   # 差 789 字节

4. 异步复制 ​

Redis 的复制是异步的:主节点写完就返回客户端成功,不等待从节点确认。

4.1 异步的代价 ​

特性说明
高性能主节点不等待从节点,写入延迟低
可能丢数据主节点宕机时,未同步到从节点的数据丢失
弱一致读从节点可能读到旧数据

数据丢失窗口 = 主节点宕瞬间,已写入但未传播到从节点的数据量。窗口大小取决于网络延迟和写入频率。

4.2 使用 WAIT 等待副本确认 ​

如果业务需要「至少 N 个从节点确认」才返回成功,可以用 WAIT 命令:

bash
SET key value
WAIT 1 5000   # 等待至少 1 个从节点确认,超时 5 秒

WAIT 用于等待复制偏移量被指定数量的副本确认。它不是事务的一部分,也不改变 Redis 默认的异步复制模型:

参数含义
numreplicas需要确认的从节点数
timeout超时时间(毫秒)

返回值是当前连接此前写入已经到达的副本数。达到超时时间时,返回超时瞬间已确认的数量;该值可能小于请求值,也可能为 0。

WAIT 不能保证绝对不丢数据(从节点确认后、主节点返回前主节点宕机,数据仍可能丢),但把丢数据的概率大幅降低了。

5. 读写分离的问题 ​

主写从读,看似完美,但生产中会遇到几个坑:

5.1 复制延迟 ​

从节点收到写命令有延迟,读从节点可能读到旧数据。

txt
主节点写入 key=1 → 100ms 后从节点才收到 → 这 100ms 内读从节点读到旧值

解决方案:

方案说明
关键读走主节点写后立即读的请求,路由到主节点
WAIT 确认写后用 WAIT 等待指定副本数确认,再决定是否读副本
接受最终一致业务能容忍短暂不一致,直接读从节点

5.2 从节点全量同步风暴 ​

多个从节点同时全量同步,主节点 fork 多次,CPU 和内存压力飙升。

txt
主节点规模 10GB(示意)
3 个从节点同时全量同步 → 多次 fork
内存峰值取决于 fork/COW 写入和磁盘、网络并发,不能只按固定倍数估算

解决方案:

方案说明
错开同步时间不要同时启动多个从节点
级联复制从节点 A 复制主节点,从节点 B 复制从节点 A
控制实例规模根据 fork、COW、磁盘和网络压测结果确定,不使用固定容量阈值

5.3 主节点宕机后的数据丢失 ​

主节点宕机后,从节点提升为新主,但原主节点中「已写入但未同步」的数据丢失。

txt
原主节点:key1, key2, key3(key3 还未同步到从节点)
原主节点宕机 → 从节点提升为新主 → key3 丢失
原主节点恢复 → 变成从节点 → 被新主的数据覆盖 → key3 彻底丢失

哨兵负责选择新主并完成切换,但不会找回原主节点尚未复制的写入(见 哨兵)。是否可接受该窗口应由 RPO 和 WAIT、持久化等保护措施共同决定。

6. 配置参考 ​

bash
# 从节点配置
replicaof <master-ip> <master-port>   # 指定主节点
replica-read-only yes                 # 从节点只读
replica-serve-stale-data yes          # 同步期间是否返回旧数据

# 主节点配置
repl-backlog-size 256mb               # 复制缓冲区大小
repl-backlog-ttl 3600                 # 无从节点后保留缓冲区的时间(秒)
min-replicas-to-write 1               # 至少 N 个从节点在线才允许写
min-replicas-max-lag 10               # 从节点最大延迟(秒)
配置含义
replica-read-only从节点只读,防止误写导致数据不一致
replica-serve-stale-datayes 允许返回同步中的旧数据,no 在无法同步时返回错误;仍需结合应用降级行为判断
min-replicas-to-write保护机制,从节点不足时禁止写入

7. 小结 ​

要点说明
全量同步首次连接或增量失败时触发,代价高(fork + RDB 传输)
增量同步断线重连后补发缺失数据,依赖复制缓冲区
异步复制主节点不等从节点确认,高性能但可能丢数据
WAIT 确认等待指定副本数确认写入,降低读到旧数据的概率
读写分离坑复制延迟、全量同步风暴、数据丢失
核心调优repl-backlog-size 要根据写入速率调整

Released under the MIT License.