1. 主从复制(Replication )
1.1 复制流程
Slave 发起同步 ──→ Master 判断同步方式 ──→ 全量或增量同步
全量同步(首次或 runid 不匹配):
1. Slave 发送 PSYNC ? -1
2. Master 执行 BGSAVE → 生成 RDB
3. Master 发送 RDB 到 Slave
4. Slave 清空内存 → 加载 RDB
5. Master 将新写入命令追加到 Replication Buffer → 发送给 Slave
6. 进入持续同步模式
增量同步(offset 在 replication backlog 内):
1. Slave 发送 PSYNC <runid> <offset>
2. Master 检查 offset 是否在 backlog 中
3. 直接发送缺失部分的命令 → Slave 执行
4. 无需全量 RDB,快速恢复
1.2 关键概念
| 概念 | 说明 |
|---|---|
| runid | Master 的唯一标识,重启后改变 |
| offset | 复制偏移量,Master 和 Slave 各自维护 |
| replication backlog | Master 的固定大小缓存区(默认 1MB),存储最近命令 |
| Replication Buffer | 每个 Slave 连接的独立输出缓存 |
# 查看复制状态
INFO replication
# role:master
# connected_slaves:2
# slave0:ip=192.168.1.2,port=6379,state=online,offset=123456,lag=0
# master_repl_offset:123456
1.3 配置
# Slave 配置
replicaof 192.168.1.1 6379
masterauth <password>
replica-read-only yes
# 允许 Slave 处理过期 key(默认)
replica-lazy-flush yes
2. Sentinel 哨兵模式
2.1 架构与职责
Client (连接 Sentinel 获取地址)
│
┌───────────────┼───────────────┐
│ │ │
Sentinel-1 Sentinel-2 Sentinel-3 (至少 3 个,奇数)
│ │ │
└───────────────┼───────────────┘
│
┌─────▼─────┐
│ Master │
└─────┬─────┘
┌────────┴────────┐
┌────▼────┐ ┌────▼────┐
│ Slave 1 │ │ Slave 2 │
└─────────┘ └─────────┘
Sentinel 职责:
1. 监控:定时 ping Master/Slave/Sentinel
2. 通知:故障时通知管理员(通过脚本)
3. 自动故障转移:Master 宕机 → 选举新 Master
4. 配置提供:客户端向 Sentinel 询问当前 Master 地址
2.2 故障转移流程
1. Sentinel-1 发现 Master 无响应(主观下线,SDOWN)
2. 询问其他 Sentinel,多数同意 → 客观下线(ODOWN)
3. 选举 Leader Sentinel(Raft 算法)
4. Leader 选择最优 Slave 提升为 Master(选择优先级最高、复制最完整的)
5. 原 Slave 重新配置为新 Master 的 Slave
6. 客户端通过 Sentinel 获取新 Master 地址
2.3 Sentinel 配置
# sentinel.conf
sentinel monitor mymaster 192.168.1.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
sentinel auth-pass mymaster password
| 参数 | 说明 |
|---|---|
monitor | 监控的 Master,最后的 2 是「同意下线」的 Sentinel 数 |
down-after-milliseconds | 无响应判定为下线的时间 |
failover-timeout | 故障转移超时 |
parallel-syncs | 同时重新配置的 Slave 数 |
2.4 脑裂问题与解决
# 脑裂:网络分区时,原 Master 和 Sentinel 断开
# 原 Master 仍在写入,但已经不被 Sentinel 认可
# 分区恢复后,原 Master 的数据被覆盖 → 数据丢失
# 解决方案:主库最小从库数 + 延迟限制
min-replicas-to-write 1 # 至少有 1 个 Slave 同步才能写入
min-replicas-max-lag 10 # Slave 延迟超过 10 秒则拒绝写入
3. 读写分离
// Spring Boot + Lettuce 读写分离
@Bean
public RedisConnectionFactory lettuceConnectionFactory() {
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(ReadFrom.REPLICA_PREFERRED) // 优先从 Slave 读
.build();
RedisStaticMasterReplicaConfiguration config =
new RedisStaticMasterReplicaConfiguration("master", 6379);
config.addNode("slave1", 6379);
config.addNode("slave2", 6379);
return new LettuceConnectionFactory(config, clientConfig);
}
// 纯 Lettuce
RedisClient client = RedisClient.create("redis://master:6379");
StatefulRedisMasterReplicaConnection<String, String> conn = MasterReplica.connect(
client, StringCodec.UTF8,
RedisURI.create("redis://slave1:6379"),
RedisURI.create("redis://slave2:6379")
);
conn.setReadFrom(ReadFrom.REPLICA_PREFERRED);
4. Redis 6.0+ 多线程 Io 对复制性能的影响
Redis 6.0 引入了多线程 Io 模型,该特性显著提升了高并发场景下的网络吞吐能力。虽然命令执行仍在主线程单线程进行,但网络读写可以被多个 Io 线程并行处理。对于主从复制场景,这意味着 Master 向多个 Slave 推送 RDB 和复制流时的网络瓶颈被大幅缓解。
多线程 Io 的核心配置如下:
# redis.conf
io-threads 4 # 启用 4 个 Io 线程
io-threads-do-reads yes # 读操作也使用多线程
在复制场景中,多线程 Io 主要带来以下收益:
- 全量同步阶段:Master 生成 RDB 后需要将其通过网络发送到 Slave。在单线程模型下,大 RDB 文件的发送会长时间占用主线程,导致其他客户端请求被阻塞。多线程 Io 将网络发送 offload 到 Io 线程,主线程可以继续处理命令。
- 增量同步阶段:持续的命令流复制对网络带宽的消耗更大。多线程并行地向多个 Slave 写数据,可以显著提升复制吞吐量,降低主从延迟。
- 注意事项:
io-threads-do-reads对复制主要是写优化,读优化在 Slave 主动发起 PSYNC 时有一定帮助。建议在有 4 核以上 CPU 且网络带宽为瓶颈时开启。对于低于 4 核的实例,开启多线程可能因线程切换开销导致性能下降。
配合 repl-disable-tcp-nodelay no,可以进一步降低复制流的网络延迟,实现低延迟的主从同步。
5. Sentinel 自动化运维脚本钩子
Sentinel 提供了两个重要的脚本钩子,可以在故障转移的关键节点触发外部脚本,实现自动化通知和客户端配置刷新。
5.1 sentinel-notification-script
该脚本在 Sentinel 产生特定事件时被调用,例如 +sdown(主观下线)、+odown(客观下线)、+failover-end(故障转移结束)。脚本接收事件类型和描述作为参数,非常适合接入企业微信、钉钉、PagerDuty 等告警通道。
# sentinel.conf
sentinel notification-script mymaster /etc/redis/notify.sh
#!/bin/bash
# /etc/redis/notify.sh
EVENT=$1
DESCRIPTION=$2
WEBHOOK_URL="https://oapi.dingtalk.com/robot/send?access_token=xxx"
curl -s -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"msgtype\": \"text\", \"text\": {\"content\": \"[Redis Sentinel] $EVENT: $DESCRIPTION\"}}"
5.2 sentinel-client-reconfig-script
该脚本在故障转移完成后触发,接收当前 Master 角色、旧地址、新地址等信息。可以在此脚本中自动更新客户端配置中心的地址、刷新负载均衡规则,甚至触发 Kubernetes Endpoint 的更新。
# sentinel.conf
sentinel client-reconfig-script mymaster /etc/redis/reconfig.sh
#!/bin/bash
# /etc/redis/reconfig.sh
MASTER_NAME=$1
ROLE=$2
OLD_IP=$3
OLD_PORT=$4
NEW_IP=$5
NEW_PORT=$6
# 更新 Consul 或 etcd 中的服务发现地址
consul kv put "redis/${MASTER_NAME}/master" "${NEW_IP}:${NEW_PORT}"
# 刷新 HAProxy 或 Nginx 后端
echo "server redis-master ${NEW_IP}:${NEW_PORT} check" > /etc/haproxy/redis-backend.cfg
systemctl reload haproxy
通过这两个钩子,Sentinel 的故障转移不再需要人工介入更改客户端配置。整个高可用链路从检测到切换再到客户端感知,形成完全自动化的闭环。这对于构建无人值守的 Redis 运维体系至关重要。
6. Redis 复制延迟监控与诊断
6.1 复制延迟来源与排查
主从延迟是 Redis 复制最常见的性能问题之一。延迟主要来源于:
- 网络抖动:Slave 与 Master 之间的网络质量不佳,导致复制数据包传输缓慢。
- CPU 与磁盘瓶颈:Slave 所在机器 CPU 满载,或者 BGSAVE 生成的 RDB 写入磁盘过慢。
- Replication Buffer 溢出:Master 写入过快,复制缓冲区被撑满,Slave 发生全量同步。
- Big Key 阻塞:一个超大 Key 的写入导致后续命令排队等待。
# 秒级监控主从延迟
redis-cli -h slave-host INFO replication | grep lag
# slave0:ip=192.168.1.2,port=6379,state=online,offset=987654321,lag=3
lag 字段表示 Slave 距离 Master 的延迟秒数,如果这个值持续增长,说明复制跟不上写入速度。
6.2 repl-backlog-size 调优
Replication Backlog 是 Master 上的一个固定大小的环形缓冲区,用于支撑增量同步。如果 Slave 短暂的网络中断后重连时,缺失的数据量超过 repl-backlog-size,就只能触发全量同步。
调优原则:
# 默认值仅 1MB,生产环境建议根据写入量调整
repl-backlog-size 64mb # 每秒写入 10MB,允许 6 秒中断内增量恢复
repl-backlog-ttl 3600 # 没有 Slave 连接时保留 backlog 1 小时
计算公式:repl-backlog-size >= 峰值写入速率 × 最大容忍断线时间 × 2。推荐按每小时最大写入量的 1/10 来估算,并预留双倍冗余。
6.3 秒级告警配置
# 使用 redis-cli + awk 实现简单的告警脚本
#!/bin/bash
LAG=$(redis-cli -h slave-host INFO replication | grep slave0 | awk -F'lag=' '{print $2}' | awk -F',' '{print $1}')
if [ "$LAG" -gt 5 ]; then
echo "Redis replication lag exceeded 5s: ${LAG}s" | mail -s "Redis Alert" ops@example.com
fi
对于 Prometheus 生态,可以使用 redis_exporter 的 redis_connected_slave_lag_seconds 指标配置 Alertmanager 规则:
- alert: RedisReplicationLagHigh
expr: redis_connected_slave_lag_seconds > 5
for: 1m
labels:
severity: critical
annotations:
summary: "Redis 主从复制延迟过高"
7. 生产环境部署拓扑与故障演练脚本
7.1 推荐的三中心部署拓扑
生产环境中,Sentinel 和 Redis 应跨可用区部署,避免单点故障。推荐的最小生产拓扑为一主两从三哨兵,分布在三个可用区:
Az-A Az-B Az-C
┌────────┐ ┌────────┐ ┌────────┐
│ Master │ │ Slave1 │ │ Slave2 │
│ :6379 │ │ :6379 │ │ :6379 │
└────────┘ └────────┘ └────────┘
┌────────┐ ┌────────┐ ┌────────┐
│Sentinel│ │Sentinel│ │Sentinel│
│ :26379│ │ :26379│ │ :26379│
└────────┘ └────────┘ └────────┘
此拓扑的优势在于:任意一个可用区整体掉线,剩余两个 Sentinel 仍可达成多数派完成故障转移,数据层也至少保留一主一从。客户端应配置所有 Sentinel 地址,并保持连接池预热。
7.2 故障演练脚本
定期的故障演练是验证高可用有效性的唯一手段。以下脚本可以模拟 Master 宕机、网络分区、Slave 故障等场景:
#!/bin/bash
# redis-failover-drill.sh
MASTER_IP="192.168.1.1"
SLAVE1_IP="192.168.1.2"
SENTINEL_IP="192.168.1.10"
echo "=== 步骤1: 记录故障前状态 ==="
redis-cli -h $MASTER_IP INFO replication > /tmp/before-replication.log
redis-cli -h $SENTINEL_IP -p 26379 SENTINEL master mymaster > /tmp/before-sentinel.log
echo "=== 步骤2: 模拟 Master 宕机 ==="
redis-cli -h $MASTER_IP DEBUG SEGFAULT 2>/dev/null || true
sleep 10
echo "=== 步骤3: 观察 Sentinel 故障转移 ==="
for i in {1..12}; do
echo "--- 第 $i 次检查 ---"
redis-cli -h $SENTINEL_IP -p 26379 SENTINEL get-master-addr-by-name mymaster
sleep 5
done
echo "=== 步骤4: 验证新 Master 可写 ==="
NEW_MASTER=$(redis-cli -h $SENTINEL_IP -p 26379 SENTINEL get-master-addr-by-name mymaster | head -1)
redis-cli -h $NEW_MASTER SET failover-drill-test "$(date)"
redis-cli -h $NEW_MASTER GET failover-drill-test
echo "=== 步骤5: 恢复旧 Master 并观察重新加入 ==="
ssh root@$MASTER_IP "systemctl start redis"
sleep 5
redis-cli -h $MASTER_IP INFO replication
echo "=== 演练完成 ==="
演练后应检查的点包括:数据一致性(新旧 Master 是否有数据分歧)、切换耗时、客户端重连成功率、Sentinel 配置是否被重写产生漂移。建议每季度执行一次全链路演练,并将结果纳入运维 SLA 考核。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。