返回首页

Redis 高可用 — 主从复制 / 哨兵 / 脑裂 / 集群踩坑

📅 创建于 2026-05-11 🔄 更新于 2026-05-12 📝 944 字

Redis 高可用

Redis 高可用三层能力:主从复制 → 数据备份 | Sentinel → 自动切换 | 脑裂处理 → 保证一致性

为什么需要高可用?

单机 Redis 的问题:机器宕机 / 进程崩溃 / 网络故障 → 缓存层失效 → 请求直打数据库 → 数据库被打爆 → 服务雪崩

高可用需要解决两个问题:数据备份(主从复制)和 自动故障切换(哨兵)。


主从复制(Replication)

架构

        Master(写)
     /    |      \
  Slave1  Slave2  Slave3(读 + 备份)
  • Master:负责写操作
  • Slave:负责读操作 + 数据备份

全量复制流程

  1. Slave 连接 Master,发送 PSYNC ? -1
  2. Master 执行 BGSAVE fork 子进程生成 RDB
  3. 将 RDB 文件发送给 Slave
  4. Slave 清空旧数据,加载 RDB
  5. 同步期间的写命令存入 replication buffer
  6. Buffer 中的增量命令继续同步给 Slave

关键: RDB + 增量命令。使用 RDB 而非直接传内存数据,因为 RDB 体积小、传输快。

增量复制(PSYNC)

Slave 短暂断线后无需全量重传。核心机制:

  • offset(复制偏移量) — 记录主从各自的数据同步位置
  • replication backlog buffer — Master 端的环形缓冲区(默认 1MB)

断线后 Slave 发送自己的 offset,Master 检查该 offset 是否还在 backlog buffer 中:

  • 是 → 只同步缺失部分(断点续传
  • 否 → 回退到全量复制
# redis.conf 主从配置
replicaof <master-ip> <master-port>   # Slave 指定主节点

# Master 配置
repl-backlog-size 1mb                 # backlog 环形缓冲区大小
repl-backlog-ttl 3600                  # 无 Slave 连接时 backlog 保留时间
replica-read-only yes                  # Slave 只读(默认)

查看复制状态

redis-cli INFO replication
# role:master
# connected_slaves:2
# slave0:ip=10.0.0.2,port=6379,state=online,offset=12345
# slave1:ip=10.0.0.3,port=6379,state=online,offset=12345

redis-cli -h <slave-ip> INFO replication
# role:slave
# master_host:10.0.0.1
# master_link_status:up
# master_last_io_seconds_ago:0

哨兵(Sentinel)

Sentinel 三大职责

职责 说明
监控 周期性检查 Redis 进程是否正常(PING/PONG)
通知 异常时告警
故障转移 Master 宕机后自动选举新 Master,更新复制关系

故障转移流程

① Sentinel 发现 Master 不可用(主观下线)
② 多个 Sentinel 投票确认(客观下线)
③ 选举新 Master(优先级 → offset → runid)
④ 其他 Slave 指向新 Master
⑤ 通知客户端新 Master 地址

配置示例

# sentinel.conf
sentinel monitor mymaster 127.0.0.1 6379 2    # 2个Sentinel同意即客观下线
sentinel down-after-milliseconds mymaster 5000 # 5秒无响应判主观下线
sentinel failover-timeout mymaster 30000       # 故障转移超时30秒
sentinel parallel-syncs mymaster 1             # 同时同步的Slave数

为什么至少部署 3 个 Sentinel?

Sentinel 自己也可能挂。至少 3 个节点保证投票多数(quorum=2),避免网络抖动导致的误判。

# 查看 Sentinel 状态
redis-cli -p 26379 SENTINEL masters
redis-cli -p 26379 SENTINEL replicas mymaster
redis-cli -p 26379 SENTINEL sentinels mymaster

脑裂(Brain Split)

什么是脑裂?

Master 网络断开但进程未死,Sentinel 认为 Master 挂了,选出新 Master → 集群中同时存在两个 Master

旧 Master(孤立 — 网络隔离但仍可接受写入)
新 Master(Sentinel 选举 — 正常服务)

危险:数据丢失

  • 网络隔离期间,客户端可能继续写旧 Master
  • 网络恢复后,旧 Master 降级为 Slave,其数据被新 Master 覆盖
  • 隔离期间的写操作全部丢失

预防方案

方案一:min-replicas-to-write + min-replicas-max-lag

# 至少 1 个 Slave 在线且延迟 < 10 秒才允许写
min-replicas-to-write 1
min-replicas-max-lag 10

效果:孤立 Master 自动拒绝写入(因为 Slave 不在线或延迟过高),从源头防止数据不一致。

方案二:缩短故障检测时间 设置合理的 down-after-milliseconds(建议 5~10 秒),减少网络分区后的双 Master 窗口期。

方案三:Redis Cluster Cluster 天然多主架构 + 节点协商机制,脑裂影响范围更小。

本质: 脑裂是 CAP 理论中 AP 与 CP 的权衡。min-replicas-to-write 是在可用性上做牺牲来换取一致性。


Sentinel vs Cluster

维度 Sentinel Cluster
高可用 ✅ 自动故障转移 ✅ 自动故障转移 + 分片
分片 ❌ 无(所有数据在一台 Master) ✅ 16384 slots 自动分片
扩容 手动加 Slave 在线水平扩展
复杂度
适用场景 单机 Redis 高可用 大数据量 + 高并发

一句话: Sentinel 解决"活着",Cluster 解决"规模"。


常见踩坑场景

事务与分布式锁的坑

坑 1:MULTI/EXEC 不会回滚

MULTI
SET name "Alice"
LPUSH name "wrong_type"    # 类型错误,这条会失败
SET age 18
EXEC
# 结果:SET name 和 SET age 都成功,LPUSH 失败,没有回滚

根本原因: Redis 事务不支持回滚。MULTI/EXEC 只保证命令顺序执行和隔离性,但运行时错误(如类型错误)不会撤销已执行的命令。

解决: 使用 WATCH + MULTI/EXEC 实现乐观锁:

WATCH balance
val = GET balance
val = val - amount
MULTI
SET balance $val
EXEC          # 如果 balance 被其他客户端修改过,EXEC 返回 nil,需重试

坑 2:分布式锁没有超时时间

SETNX lock:order:123 "locked"    # 获取锁
# ... 业务逻辑 ...
DEL lock:order:123               # 释放锁 — 如果业务逻辑崩溃,锁永远不释放

解决: 设置锁超时 + 唯一标识防误删:

SET lock:order:123 <unique_id> NX EX 30   # 30秒自动过期

# 释放时用 Lua 脚本保证原子性(先判断是否本线程的锁,再删除)
# -- 释放锁脚本
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

参考 redis-connection-management 中的连接与熔断治理。

主从复制的坑

坑 3:新加 slave 节点导致 master 内存翻倍

全量复制流程中,Master 需要 BGSAVE 生成 RDB + 将同步期间的写操作存入 replication buffer。RDB 生成时 fork 子进程依赖 Copy-on-Write,如果同步期间有大量写入,OS 会复制内存页,导致 Master 内存飙高。

# 查看当前复制状态
redis-cli INFO replication | grep sync
# sync_partial_ok:0
# sync_partial_err:0
# sync_full:1          # 全量同步 → 内存翻倍是预期的

预防:

  • 给 Master 预留至少 30% 内存余量(考虑 fork + COW 峰值)
  • 低峰期执行 REPLICAOF(避免业务高峰期触发全量同步)
  • 减小 repl-backlog-size(控制 buffer 内存使用)

坑 4:slave master_link_status:down — 主从连接中断

redis-cli INFO replication
# master_link_status:down
# master_last_io_seconds_ago:-1

最常见原因:

  1. Master 设置了密码但 Slave 未配置 masterauth
  2. 防火墙/安全组拦截了 6379 端口
  3. replicaof 配置的 IP 或端口写错了

排查:

# 在 Slave 上测试网络连通性
redis-cli -h <master-ip> -p 6379 -a <password> PING

# 动态修复(无需重启)
redis-cli CONFIG SET masterauth <password>
redis-cli REPLICAOF <master-ip> 6379

哨兵的坑

坑 5:quorum 配置不当,Master 宕机后不切换

# ❌ 只有 2 个哨兵 + quorum=2:其中一个哨兵异常时,剩余 1 票不够 quorum
sentinel monitor mymaster 127.0.0.1 6379 2

# ✅ 至少 3 个哨兵 + quorum=2:即使 1 个哨兵异常,仍有 2 票可达成 quorum

根本原因: quorum=N 要求至少 N 个哨兵认为 Master 挂了才触发故障转移。如果哨兵总数 < quorum × 2 - 1,在部分哨兵故障时将无法触发切换。

推荐部署: 3 个哨兵 + quorum=2,或 5 个哨兵 + quorum=3。

坑 6:Redis Cluster MOVED / CROSSSLOT 错误

# MOVED:客户端未使用集群模式,直连了非目标节点
MOVED 7638 192.168.1.102:6379

# CROSSSLOT:多 key 操作涉及不同 slot,集群不支持跨 slot 操作
CROSSSLOT Keys in request don't hash to the same slot

解决 MOVED: 确保客户端开启集群模式(如 Spring Boot 配置 spring.redis.cluster.nodes + max-redirects)。

解决 CROSSSLOT: 使用 Hash Tag,强制关联 key 落入同一 slot:

# 不使用 Hash Tag → key1 和 key2 可能在不同 slot
MSET key1 val1 key2 val2    # CROSSSLOT

# 使用 Hash Tag → {user:123} 部分计算 slot,两者必然在同一 slot
MSET {user:123}:name Alice {user:123}:age 18   # ✅

常见排查命令速查

# 复制状态
redis-cli INFO replication
# role: master | slave
# master_link_status: up | down

# Sentinel 状态
redis-cli -p 26379 SENTINEL masters

# 主从延迟(秒)
redis-cli INFO replication | grep master_last_io_seconds_ago

# 手动触发故障转移
redis-cli -p 26379 SENTINEL FAILOVER mymaster

# 查看当前角色
redis-cli ROLE

关联页面

页面 关联点
redis-connection-management 连接管理与熔断(哨兵故障转移期间的保护);分布式锁设计参考
redis-persistence-strategy 全量复制使用 RDB,持久化是复制的基础
redis-backup-recovery 备份应在从库执行,主从不是备份的替代
redis-memory-optimization 内存优化(淘汰策略场景参考)

Redis 体系扩展: 持久化 → 备份恢复 → 连接管理 → 内存优化 → 高可用踩坑,覆盖 Redis 运维的核心维度 + 实操排障。