主从复制
单机 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 补齐数据;覆盖窗口不足时,从节点重连后需要重新全量同步。全量同步期间还要结合副本输出缓冲区、网络和磁盘能力评估容量。
# 查看缓冲区大小
redis-cli INFO replication | grep repl_backlog_size
# 调整大小(默认 1MB,生产环境建议调大)
# redis.conf
repl-backlog-size 256mb缓冲区大小应按可接受的断线窗口和峰值写入速率计算:
repl-backlog-size ≥ 可接受断线时长(s)× 峰值复制写入速率(B/s)× 余量系数下表仅演示公式,不是通用推荐值。实际配置应代入峰值写入速率、网络抖动窗口、重连时间和内存预算:
| 场景 | 峰值写入速率 | 可接受断线时长 | 估算值(含 1.5 倍余量) |
|---|---|---|---|
| 低写入 | 1 MB/s | 300s | 约 450 MB |
| 中等写入 | 10 MB/s | 300s | 约 4.5 GB |
| 高写入 | 50 MB/s | 600s | 约 45 GB |
3. 增量同步
断线重连后,如果从节点的偏移量还在主节点的复制缓冲区范围内,就只补发缺失部分。
3.1 PSYNC 机制
从节点发送:PSYNC <runid> <offset>
主节点检查:
- runid 匹配 && offset 在缓冲区内 → +CONTINUE <offset>,增量同步
- runid 不匹配 || offset 不在缓冲区 → +FULLRESYNC,全量同步3.2 偏移量与积压缓冲区
| 概念 | 说明 |
|---|---|
| 复制偏移量(replication offset) | 主从各自维护,记录已复制的字节数 |
| 积压缓冲区(repl_backlog) | 主节点的环形缓冲区,保存最近的写命令 |
| 命令传播 | 全量同步后,主节点持续把写命令传播给从节点 |
主节点每次写入时,同时写入积压缓冲区并推进偏移量。从节点每收到一批命令,也推进自己的偏移量。两者之差表示尚未复制的字节数;要换算成时间延迟,还需要结合当前复制速率。
# 查看主从偏移量差
redis-cli INFO replication
# master_repl_offset: 123456789
# slave_repl_offset: 123456000 # 差 789 字节4. 异步复制
Redis 的复制是异步的:主节点写完就返回客户端成功,不等待从节点确认。
4.1 异步的代价
| 特性 | 说明 |
|---|---|
| 高性能 | 主节点不等待从节点,写入延迟低 |
| 可能丢数据 | 主节点宕机时,未同步到从节点的数据丢失 |
| 弱一致 | 读从节点可能读到旧数据 |
数据丢失窗口 = 主节点宕瞬间,已写入但未传播到从节点的数据量。窗口大小取决于网络延迟和写入频率。
4.2 使用 WAIT 等待副本确认
如果业务需要「至少 N 个从节点确认」才返回成功,可以用 WAIT 命令:
SET key value
WAIT 1 5000 # 等待至少 1 个从节点确认,超时 5 秒WAIT 用于等待复制偏移量被指定数量的副本确认。它不是事务的一部分,也不改变 Redis 默认的异步复制模型:
| 参数 | 含义 |
|---|---|
numreplicas | 需要确认的从节点数 |
timeout | 超时时间(毫秒) |
返回值是当前连接此前写入已经到达的副本数。达到超时时间时,返回超时瞬间已确认的数量;该值可能小于请求值,也可能为 0。
WAIT不能保证绝对不丢数据(从节点确认后、主节点返回前主节点宕机,数据仍可能丢),但把丢数据的概率大幅降低了。
5. 读写分离的问题
主写从读,看似完美,但生产中会遇到几个坑:
5.1 复制延迟
从节点收到写命令有延迟,读从节点可能读到旧数据。
主节点写入 key=1 → 100ms 后从节点才收到 → 这 100ms 内读从节点读到旧值解决方案:
| 方案 | 说明 |
|---|---|
| 关键读走主节点 | 写后立即读的请求,路由到主节点 |
| WAIT 确认 | 写后用 WAIT 等待指定副本数确认,再决定是否读副本 |
| 接受最终一致 | 业务能容忍短暂不一致,直接读从节点 |
5.2 从节点全量同步风暴
多个从节点同时全量同步,主节点 fork 多次,CPU 和内存压力飙升。
主节点规模 10GB(示意)
3 个从节点同时全量同步 → 多次 fork
内存峰值取决于 fork/COW 写入和磁盘、网络并发,不能只按固定倍数估算解决方案:
| 方案 | 说明 |
|---|---|
| 错开同步时间 | 不要同时启动多个从节点 |
| 级联复制 | 从节点 A 复制主节点,从节点 B 复制从节点 A |
| 控制实例规模 | 根据 fork、COW、磁盘和网络压测结果确定,不使用固定容量阈值 |
5.3 主节点宕机后的数据丢失
主节点宕机后,从节点提升为新主,但原主节点中「已写入但未同步」的数据丢失。
原主节点:key1, key2, key3(key3 还未同步到从节点)
原主节点宕机 → 从节点提升为新主 → key3 丢失
原主节点恢复 → 变成从节点 → 被新主的数据覆盖 → key3 彻底丢失哨兵负责选择新主并完成切换,但不会找回原主节点尚未复制的写入(见 哨兵)。是否可接受该窗口应由 RPO 和 WAIT、持久化等保护措施共同决定。
6. 配置参考
# 从节点配置
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-data | yes 允许返回同步中的旧数据,no 在无法同步时返回错误;仍需结合应用降级行为判断 |
min-replicas-to-write | 保护机制,从节点不足时禁止写入 |
7. 小结
| 要点 | 说明 |
|---|---|
| 全量同步 | 首次连接或增量失败时触发,代价高(fork + RDB 传输) |
| 增量同步 | 断线重连后补发缺失数据,依赖复制缓冲区 |
| 异步复制 | 主节点不等从节点确认,高性能但可能丢数据 |
| WAIT 确认 | 等待指定副本数确认写入,降低读到旧数据的概率 |
| 读写分离坑 | 复制延迟、全量同步风暴、数据丢失 |
| 核心调优 | repl-backlog-size 要根据写入速率调整 |