Redis 高可用
Redis 高可用三层能力:主从复制 → 数据备份 | Sentinel → 自动切换 | 脑裂处理 → 保证一致性
为什么需要高可用?
单机 Redis 的问题:机器宕机 / 进程崩溃 / 网络故障 → 缓存层失效 → 请求直打数据库 → 数据库被打爆 → 服务雪崩。
高可用需要解决两个问题:数据备份(主从复制)和 自动故障切换(哨兵)。
主从复制(Replication)
架构
Master(写)
/ | \
Slave1 Slave2 Slave3(读 + 备份)
- Master:负责写操作
- Slave:负责读操作 + 数据备份
全量复制流程
- Slave 连接 Master,发送
PSYNC ? -1 - Master 执行
BGSAVEfork 子进程生成 RDB - 将 RDB 文件发送给 Slave
- Slave 清空旧数据,加载 RDB
- 同步期间的写命令存入 replication buffer
- 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
最常见原因:
- Master 设置了密码但 Slave 未配置
masterauth - 防火墙/安全组拦截了 6379 端口
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 运维的核心维度 + 实操排障。