redis哨兵不采用raft算法,而是基于quorum的轻量级投票机制实现领头哨兵选举,无日志复制、不维护状态机、epoch仅用于幂等判断,且每次故障转移均需重新投票。

Redis 哨兵(Sentinel)不使用 Raft 算法,它自己实现了一套基于多数派投票的领头哨兵选举机制——和 Raft 有相似目标(如强多数、防止脑裂),但细节完全不同。直接套用 Raft 概念去理解哨兵选主,反而容易误解行为。
为什么 Sentinel 不是 Raft
Sentinel 的选举逻辑更轻量、无日志复制、不维护状态机,也不要求严格有序的任期(epoch)推进。它的 currentEpoch 和 failoverEpoch 是单调递增的整数,仅用于冲突消解和幂等判断,不构成 Raft 那样的“任期+日志匹配”双重校验。
常见错误现象:No failover in progress 或多个哨兵各自发起故障转移,本质是多数派未达成一致,不是 Raft 中的“选举超时重试”问题。
- Raft 要求候选者先拉取最新日志再参选;Sentinel 完全不看主从复制偏移,只比对主观下线(sdown)和客观下线(odown)状态
- Raft 投票响应必须包含自身日志信息;Sentinel 的
SENTINEL is-master-down-by-addr响应只返回是否同意下线 + 当前 epoch,不携带数据一致性证据 - Raft 一旦选出 Leader 就长期稳定;Sentinel 的领头哨兵只对单次故障转移有效,下次故障重新投票
实际选举流程:谁投给谁、什么时候停
选举触发于某个哨兵确认主节点为 odown(即足够多哨兵报告 sdown),随后它会自增 currentEpoch 并向其他哨兵发送 SENTINEL is-master-down-by-addr 请求,试图争取投票。
关键点在于“多数派”的计算方式:不是哨兵总数的 1/2+1,而是配置中 quorum 值(在 sentinel monitor 行指定)与在线哨兵数的较小值。例如:sentinel monitor mymaster 127.0.0.1 6379 3,但当前只有 4 个哨兵在线,则需至少 3 票才能通过 odown 判定;若只剩 2 个在线,quorum 自动降为 2,此时 2 票即满足。
- 每个哨兵对同一
failoverEpoch只投一票,且只投给第一个合法请求(防重复投票) - 发起者收到 >=
quorum张票后,立即开始执行故障转移,不再等待其余哨兵响应 - 若超时未达票数,该轮选举失败,稍后可能由另一个哨兵重试(不保证同一轮重试)
容易踩的坑:quorum、down-after-milliseconds 和网络分区
最常被忽略的是 quorum 和哨兵实际在线数不匹配导致的“无法达成 odown”或“误判脑裂”。比如部署 5 个哨兵,quorum 设为 3,但因网络分区,某哨兵只能连通 2 个同组成员——它永远凑不够 3 票,也就永远不会触发故障转移,哪怕主库真挂了。
-
down-after-milliseconds过短(如设为 1000)会导致网络抖动时频繁误报sdown,引发无效投票风暴 - 所有哨兵必须配置完全相同的
sentinel monitor参数,否则对同一主库的quorum计算结果不一致,投票逻辑错乱 - 哨兵之间依赖 TCP 心跳(默认每 2 秒发一次
PING),丢包率高或防火墙拦截PING会导致sdown误判,进而污染投票基础
怎么验证当前领头哨兵是谁
没有中心化命令直接查“当前 leader”,但可通过组合操作推断:任意哨兵执行 SENTINEL sentinels <master-name></master-name>,查看各哨兵上报的 flags 字段中是否有 is-head-sentinels(注意拼写,官方文档写错成 head-sentinel,实际返回是 is-head-sentinels);更可靠的方式是观察日志中类似 +failover-detected 和 +failover-end 的事件归属哪个哨兵 ID。
示例:
SENTINEL sentinels mymaster 1) 1) "name" 2) "127.0.0.1:26380" 3) "flags" 4) "slave,is-head-sentinels" ← 这个哨兵当前是领头者(仅针对本次 failover)
注意:is-head-sentinels 不是永久身份,只是最近一次成功故障转移的执行者标识;它不会广播给其他哨兵,也不会持久化,重启后清空。
真正难处理的是跨机房部署时,quorum 设置和网络延迟的平衡——这地方没标准答案,得按你的可用性目标和分区容忍度反复调。










