凌晨三点的主库宕机,我不希望你亲历
讲一次没有哨兵的凌晨。主库失联告警响起,我蹬着 VPN 上服务器:确认进程、确认机器、挑一个从库、改主从关系、改应用配置、重启、观察流量……一整套人肉操作做完,二十分钟过去了,业务就这么停了二十分钟。更要命的是手抖的风险——选错从库、漏改配置,都可能把事故变成灾难。
如果当时有哨兵(Sentinel),这二十分钟会被压缩成三十秒:主库失联、多数哨兵确认、自动选新主、通知客户端,全程没人碰键盘。这篇就把哨兵这套自动化流程拆开看。
哨兵是什么:不存数据的观察员
哨兵是独立于 Redis 实例运行的小进程,自己不存任何业务数据,专职干四件事:
| 任务 | 说明 |
|---|---|
| 监控 | 每秒 ping 一遍主库、从库和其他哨兵 |
| 通知 | 实例出问题时,把消息推给运维或客户端 |
| 自动故障转移 | 主库确认下线后,从从库里选一个晋升为新主 |
| 配置中心 | 客户端来问「主库是谁」,哨兵随时答得上来 |
一个关键的设计:哨兵自己也是分布式的,至少部署 3 个、且是奇数。一个哨兵自己挂了就误报,两个哨兵 disagreement 时永远对不齐——奇数个才能凑出稳定的「多数派」。这条规则贯穿全文的每个环节。
判定下线:先主观,再客观
哨兵怎么确认「主库真的挂了」?分两阶段。某个哨兵 ping 主库超过 down-after-milliseconds 没回音,它标记主库为主观下线——注意,这只是它一个人的看法,也可能是它自己网断了。于是它去问别的哨兵:你们也 ping 不通吗?当超过 quorum 个哨兵都同意,升级为客观下线——大家投票确认,这主库是真不行了。
为什么要两阶段?想象主库没事,只是某个哨兵和主库之间的网络断了:单哨兵直接切换就是误杀。多数派投票机制保证了「一票否决」式的误判不会发生——这是把「防误删别人的锁」(第 8 篇)的思路用到了集群管理上:重要结论,不能由单点说了算。
谁来执行切换:哨兵也要先选领导
确认客观下线后,还有一个问题:这么多哨兵,谁动手执行切换?答案还是投票——哨兵之间先选出一个领导者(内部是一套类 Raft 选举),拿到多数派(majority)选票的哨兵当选,由它执行故障转移。
这里有两个容易混的数字:quorum 只用来判定「客观下线」,majority 用来选出「领导者」。3 个哨兵配 quorum=2 时,判定下线要 2 票,选领导要 2 票(3 的多数);5 个哨兵配 quorum=3,判定要 3 票,选领导要 3 票。但两者含义不同——quorum 可以小于多数派,比如 5 个哨兵配 quorum=2:判定下线更容易(2 票就行),选领导依然要 3 票。
领导者上任后执行四步:
- 挑新主:规则三条,优先级(replica-priority,数字小的优先,设 0 表示永不参选)→ 复制进度(offset 最大、数据最全)→ runid 最小(兜底的随机平局裁决)。
- 改换门庭:让其余从库执行 replicaof 新主。
- 处置旧主:旧主恢复后自动降级为从库,跟着新主复制。
- 通知客户端:把新主的地址广播出去。
脑裂:切换期间最疼的坑
现在看最阴险的场景。主库没挂,只是它和哨兵、从库之间的网络断了(机房级网络分区):哨兵们按流程判定客观下线、选出新主、客户端开始往新主写数据;而旧主那边还活着,孤岛上照样在接写请求。等网络恢复,旧主被降级为从库、清空数据跟新主同步——它孤岛上收到的那些写入,就这么没了。
缓解配置是第 11 篇提过的老朋友:min-replicas-to-write + min-replicas-max-lag——主库发现没有从库跟上,就拒绝写入。孤岛上的旧主接不了写,数据就不至于丢。但这只是止血:真正的兜底还是那句老话,关键数据以数据库为准,Redis 不做唯一的真相源。
配置与客户端接入
三个哨兵节点的核心配置,每个节点一份:
# sentinel.conf:监控名为 mymaster 的主库,quorum 为 2
sentinel monitor mymaster 10.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1客户端不再直连主库,而是「问哨兵要主库地址」并跟随切换。Lettuce 的接法:
// 客户端连哨兵,自动发现主库并跟随故障转移
RedisURI uri = RedisURI.builder()
.withSentinel("10.0.0.11", 26379)
.withSentinel("10.0.0.12", 26379)
.withSentinel("10.0.0.13", 26379)
.withMaster("mymaster")
.build();注意连接字符串里写的全是哨兵地址和主库名,没有一个真正的 Redis 数据节点——主库是谁,运行时问哨兵,切换了它自然会告诉你新的。
总结:哨兵用「多数派」贯穿始终——判定下线要多数同意,选领导要多数投票,奇数个节点才玩得转;故障转移四步走,脑裂用写入限流止血。但哨兵解决的是「高可用」,不是「容量」:数据再多,主从还是一份数据。数据量大到单机装不下怎么办?下一篇聊聊 Cluster 集群:数据怎么切片?哈希槽是什么?为什么 Redis 作者放弃了一致性哈希?下篇见。
咖啡凉了,记得趁热喝。
评论 (0)