主从复制风暴与优化
「为什么主节点 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 突然飙升,从节点全部不可用。
原因:
- 零点是流量高峰,写入量暴增
- 某个从节点网络抖动,断开连接
- 网络恢复后,该从节点发起全量同步
- 同时其他从节点也因为延迟超时发起全量同步
- 主节点同时处理多个 RDB 生成,CPU 打满
解决:
- 增大
repl-backlog-size到 100MB - 启用无盘复制
- 添加更多从节点分摊压力
- 优化网络配置
案例二:扩容从节点导致雪崩
背景:业务扩展,需要从 3 个从节点扩展到 10 个。
操作:
bash
# 一次性配置所有新从节点
for i in {4..10}; do
redis-cli -h slave$i REPLICAOF master 6379
done结果:10 个 RDB 同时传输,主节点内存暴涨,OOM。
解决:
- 串行添加从节点,每批间隔 5 分钟
- 预先增大主节点内存
- 使用层级复制架构
最佳实践总结
| 场景 | 建议 |
|---|---|
| 小规模部署(<5 从节点) | 直接配置,注意积压缓冲区大小 |
| 中等规模(5-20 从节点) | 无盘复制 + 层级复制 |
| 大规模部署(>20 从节点) | 层级复制 + 哨兵 + 应用层控制 |
| 频繁网络抖动环境 | 增大 repl-timeout + 积压缓冲区 |
总结
主从复制风暴是生产环境的常见问题:
- 原因:多个从节点同时全量同步
- 危害:CPU、内存、网络压力暴增
- 解决:无盘复制、串行添加、层级复制、合理配置
留给你的问题
假设你有一个 Redis 主从架构,主节点突然宕机,Sentinel 选出了一个从节点晋升为新主节点。
问题:原来的主节点恢复后,它会变成新主节点的从节点吗?还是变成一个独立的节点?Redis 是如何处理的?
