Redis 主从复制与 Sentinel 高可用

深入 Redis 主从复制原理(全量/增量同步、复制偏移量)、Sentinel 哨兵自动故障转移机制与读写分离架构实践。

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 关键概念

概念说明
runidMaster 的唯一标识,重启后改变
offset复制偏移量,Master 和 Slave 各自维护
replication backlogMaster 的固定大小缓存区(默认 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 考核。


延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「redis」更多文章

  1. 多租户隔离与资源配额:共享 Redis 的边界设计
  2. Key 设计与命名规范:Redis 里唯一的结构约束
  3. 代理与路由方案:客户端直连之外的另一种选择