Skip to content

主从复制风暴与优化

「为什么主节点 CPU 突然飙升?」

「为什么添加一个从节点后,主节点压力暴增?」

「为什么从节点越多,性能反而下降?」

这些问题,可能都是主从复制风暴造成的。

什么是主从复制风暴?

简单说:多个从节点同时向主节点发起同步请求,导致主节点压力过大。

┌─────────────────────────────────────────────────────────────────┐
│                      没有复制风暴时                               │
│                                                                 │
│         ┌─────────────┐                                         │
│         │    从节点1   │                                         │
│         └──────┬──────┘                                         │
│                │                                                  │
│         ┌──────▼──────┐        ┌─────────────┐                  │
│         │    主节点     │◀───────│    从节点2   │                  │
│         │  压力: 1x    │        └─────────────┘                  │
│         └─────────────┘                                         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                      发生复制风暴时                               │
│                                                                 │
│    ┌─────────────┐  ┌─────────────┐  ┌─────────────┐            │
│    │    从节点1   │  │    从节点2   │  │    从节点3   │            │
│    └──────┬──────┘  └──────┬──────┘  └──────┬──────┘            │
│           │                │                │                     │
│           └────────────────┼────────────────┘                     │
│                            │                                      │
│                     ┌──────▼──────┐                               │
│                     │    主节点     │                              │
│                     │  压力: 10x   │                              │
│                     └─────────────┘                               │
│                            │                                      │
│              ┌─────────────┼─────────────┐                        │
│              │             │             │                         │
│         ┌────▼────┐  ┌────▼────┐  ┌────▼────┐                     │
│         │  等待   │  │  等待   │  │  等待   │                      │
│         │  RDB   │  │  RDB   │  │  RDB   │                      │
│         └─────────┘  └─────────┘  └─────────┘                      │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

复制风暴的触发场景

场景一:主节点宕机后恢复

1. 主节点宕机
2. 从节点检测到主节点不可用
3. Sentinel 选出一个从节点晋升为新主节点
4. 其他从节点向新主节点发起复制请求
5. 如果从节点很多,瞬间多个 RDB 传输...

场景二:网络抖动导致从节点断连

1. 网络抖动,从节点与主节点断开
2. 网络恢复后,多个从节点同时重连
3. 多个从节点同时请求全量同步
4. 主节点同时执行多个 BGSAVE

场景三:添加大量从节点

1. 业务扩展,添加 10 个新从节点
2. 一次性配置所有从节点连接主节点
3. 10 个 RDB 同时传输给主节点
4. 主节点资源耗尽

复制风暴的危害

危害说明
CPU 飙升多个 BGSAVE 同时执行,CPU 打满
内存暴涨COW 机制导致内存可能翻 N 倍
网络阻塞大量 RDB 传输占用带宽
从节点不可用从节点长时间处于同步状态,无法响应请求
主节点宕机资源耗尽导致主节点也挂掉

优化方案

方案一:无盘复制(Diskless Replication)

bash
# redis.conf
repl-diskless-sync yes
repl-diskless-sync-delay 5

原理:主节点不生成 RDB 文件,直接通过 socket 发送给从节点。

┌─────────────────────────────────────────────────────────────────┐
│                      无盘复制流程                                 │
│                                                                 │
│   主节点                                                         │
│   ┌─────────────────────────────────────────────────────────┐  │
│   │ fork 子进程                                               │  │
│   │      │                                                    │  │
│   │      │ 直接通过 socket 发送数据                          │  │
│   │      ▼                                                    │  │
│   │   [Socket 连接] ──▶ 从节点1                              │  │
│   │               ──▶ 从节点2                                │  │
│   │               ──▶ 从节点3                                │  │
│   └─────────────────────────────────────────────────────────┘  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

优点:避免磁盘 I/O,适合磁盘性能差的机器。 缺点:如果从节点处理慢,主节点需要保存所有待发送数据。

方案二:串行复制(增量配置)

不要一次性添加所有从节点,而是逐个添加

bash
# 添加从节点 1
redis-cli -h <slave1> REPLICAOF <master-ip> 6379
# 等待同步完成...

# 添加从节点 2
redis-cli -h <slave2> REPLICAOF <master-ip> 6379
# 等待同步完成...

# 添加从节点 3
redis-cli -h <slave3> REPLICAOF <master-ip> 6379
# ...

方案三:层级复制(主-从-从)

┌─────────────────────────────────────────────────────────────────┐
│                        主节点                                    │
│                            │                                    │
│                            │ 复制                                │
│                            ▼                                    │
│                       ┌─────────┐                              │
│                       │ 从节点1  │                              │
│                       └────┬────┘                              │
│                            │ 复制                               │
│              ┌─────────────┼─────────────┐                      │
│              ▼             ▼             ▼                      │
│         ┌────────┐    ┌────────┐    ┌────────┐                  │
│         │ 从节点2 │    │ 从节点3 │    │ 从节点4 │                  │
│         └────────┘    └────────┘    └────────┘                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

配置

bash
# 从节点 2 配置
replicaof <master-ip> 6379

# 从节点 3 配置(指向从节点 1)
replicaof <slave1-ip> 6379

# 从节点 4 配置(指向从节点 1)
replicaof <slave1-ip> 6379

优点

  • 分担主节点压力
  • 减少网络流量

缺点

  • 层级越深,延迟越大
  • 架构复杂

方案四:调整复制超时时间

bash
# redis.conf

# 复制超时时间(从节点多久没收到主节点消息认为超时)
repl-timeout 60

# 积压缓冲区大小(足够大可以支持部分同步)
repl-backlog-size 100mb

方案五:使用 Redis Sentinel

Sentinel 可以控制故障转移行为,避免同时多从节点重连:

bash
# sentinel.conf

# 新主节点晋升后,从节点多久开始复制(错开时间)
down-after-milliseconds 30000
 failover-timeout 180000

# 每次故障转移后,重新配置从节点的时间
# Sentinel 会错开时间配置,避免同时复制

方案六:限制并发复制

Redis 本身不提供直接限制并发复制的配置,但可以通过应用层控制

java
public class ReplicationController {
    
    private int currentReplicatingCount = 0;
    private static final int MAX_CONCURRENT_REPLICATION = 3;
    
    /**
     * 尝试发起复制
     * 如果当前正在复制的从节点过多,等待
     */
    public void startReplication(String slaveId) {
        synchronized (this) {
            while (currentReplicatingCount >= MAX_CONCURRENT_REPLICATION) {
                wait();
            }
            currentReplicatingCount++;
        }
        
        try {
            // 执行复制
            executeReplication(slaveId);
        } finally {
            synchronized (this) {
                currentReplicatingCount--;
                notifyAll();
            }
        }
    }
}

复制风暴的监控

监控指标

bash
# 查看主节点的复制状态
redis-cli INFO replication

输出:

role:master
connected_slaves:5
slave0:ip=127.0.0.1,port=6380,state=online,offset=123456,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=123456,lag=0
slave2:ip=127.0.0.1,port=6382,state=online,offset=123456,lag=0
slave3:ip=127.0.0.1,port=6383,state=wait_bgsave,offset=0,lag=0
slave4:ip=127.0.0.1,port=6384,state=wait_bgsave,offset=0,lag=0

# 查看复制延迟
 replication_backlog_active:1
 replication_backlog_size:10485760
 replication_backlog_histlen:2048
 replication_backlog_first_byte_offset:121500
 replication_backlog_offset:123456

关键指标解读

状态说明
state=online正常复制中
state=wait_bgsave等待主节点完成 BGSAVE
state=connect正在连接主节点
lag=0复制延迟正常
lag=10复制延迟 10 秒

告警规则

yaml
# Prometheus 告警规则
groups:
  - name: redis_replication_alerts
    rules:
      - alert: RedisReplicationLag
        expr: redis_replication_backlog_lag_seconds > 5
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "Redis 复制延迟超过 5 秒"
          
      - alert: RedisReplicationBacklogFull
        expr: redis_replication_backlog_histlen / redis_replication_backlog_size > 0.9
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Redis 复制积压缓冲区即将满"

实际案例分析

案例一:双十一零点复制风暴

背景:某电商双十一零点,Redis 主节点 CPU 突然飙升,从节点全部不可用。

原因

  1. 零点是流量高峰,写入量暴增
  2. 某个从节点网络抖动,断开连接
  3. 网络恢复后,该从节点发起全量同步
  4. 同时其他从节点也因为延迟超时发起全量同步
  5. 主节点同时处理多个 RDB 生成,CPU 打满

解决

  1. 增大 repl-backlog-size 到 100MB
  2. 启用无盘复制
  3. 添加更多从节点分摊压力
  4. 优化网络配置

案例二:扩容从节点导致雪崩

背景:业务扩展,需要从 3 个从节点扩展到 10 个。

操作

bash
# 一次性配置所有新从节点
for i in {4..10}; do
    redis-cli -h slave$i REPLICAOF master 6379
done

结果:10 个 RDB 同时传输,主节点内存暴涨,OOM。

解决

  1. 串行添加从节点,每批间隔 5 分钟
  2. 预先增大主节点内存
  3. 使用层级复制架构

最佳实践总结

场景建议
小规模部署(<5 从节点)直接配置,注意积压缓冲区大小
中等规模(5-20 从节点)无盘复制 + 层级复制
大规模部署(>20 从节点)层级复制 + 哨兵 + 应用层控制
频繁网络抖动环境增大 repl-timeout + 积压缓冲区

总结

主从复制风暴是生产环境的常见问题:

  • 原因:多个从节点同时全量同步
  • 危害:CPU、内存、网络压力暴增
  • 解决:无盘复制、串行添加、层级复制、合理配置

留给你的问题

假设你有一个 Redis 主从架构,主节点突然宕机,Sentinel 选出了一个从节点晋升为新主节点。

问题:原来的主节点恢复后,它会变成新主节点的从节点吗?还是变成一个独立的节点?Redis 是如何处理的?

基于 VitePress 构建