文章目录
-
- [一、 为什么引入哨兵模式?(诞生原因)](#一、 为什么引入哨兵模式?(诞生原因))
- [二、 哨兵模式的架构原理与三大核心任务](#二、 哨兵模式的架构原理与三大核心任务)
-
- [1. 拓扑发现(每 10 秒执行 INFO)](#1. 拓扑发现(每 10 秒执行 INFO))
- [2. 哨兵集群自发现(每 2 秒执行 Pub/Sub)](#2. 哨兵集群自发现(每 2 秒执行 Pub/Sub))
- [3. 绝对健康检查(每 1 秒执行 PING)](#3. 绝对健康检查(每 1 秒执行 PING))
- [三、 状态判定:主观下线与客观下线](#三、 状态判定:主观下线与客观下线)
-
- [1. 主观下线(Subjective Down, SDOWN)](#1. 主观下线(Subjective Down, SDOWN))
- [2. 客观下线(Objective Down, ODOWN)](#2. 客观下线(Objective Down, ODOWN))
- [四、 核心工作流:从选举到故障转移](#四、 核心工作流:从选举到故障转移)
-
- [1. 第一阶段:哨兵领导者(Leader)选举](#1. 第一阶段:哨兵领导者(Leader)选举)
- [2. 第二阶段:新主节点挑选算法(从机上位)](#2. 第二阶段:新主节点挑选算法(从机上位))
- [3. 第三阶段:身份转换与拓扑重构](#3. 第三阶段:身份转换与拓扑重构)
- [4. 第四阶段:客户端重定向(配置中心功能)](#4. 第四阶段:客户端重定向(配置中心功能))
- [五、 哨兵模式的痛点:为什么不能保证数据零丢失?](#五、 哨兵模式的痛点:为什么不能保证数据零丢失?)
一、 为什么引入哨兵模式?(诞生原因)
在单纯的 主从复制(Master-Slave) 架构中,主要解决了数据冗余备份和读写分离(分担读压力)的问题。
然而,主从架构无法保证真正的高可用 。一旦主节点(Master)宕机,整个集群将失去写能力。此时必须进行人工干预:手动挑选一个从节点晋升为主节点,并通知所有客户端修改连接配置,最后让其余从节点指向新主。这种方式响应慢、极易出错,无法满足企业级 24 小时连续服务的需求。
为了实现自动化的故障发现、自动故障转移(Failover)以及集中式配置中心 ,Redis 官方引入了 哨兵模式(Sentinel)。
二、 哨兵模式的架构原理与三大核心任务
哨兵(Sentinel)是一个特殊的独立进程 ,它不存储业务数据,而是作为"监督者"部署在数据节点之外。多个哨兵节点相互通信,形成一个分布式的哨兵集群。
很多人误以为哨兵集群只做单纯的"健康体检",其实在底层的分布式设计中,哨兵通过三个不同周期的定时任务 ,完美闭环了拓扑发现、信息共享、死活监控三大核心问题:
1. 拓扑发现(每 10 秒执行 INFO)
-
运行机制: 哨兵每 10 秒向 Master 发送一次
INFO replication命令。 -
核心价值: 在
sentinel.conf中,我们只需要配置主节点的 IP 和端口 。哨兵通过解析 Master 返回的INFO响应,能够自动发现该 Master 下属的所有 Slave 列表。这实现了主从拓扑结构的动态感知。
2. 哨兵集群自发现(每 2 秒执行 Pub/Sub)
-
运行机制: 哨兵每 2 秒利用 Redis 的发布/订阅(Pub/Sub)机制,向主从节点的
__sentinel__:hello频道发送一条消息,内容包含自己的 IP、端口、运行 ID 以及对 Master 的状态看法。同时,它也会订阅这个频道。 -
核心价值: 哨兵在配置时不需要指定其他哨兵的 IP。通过这个公共频道,哨兵之间能够自动发现同伙,并建立起 P2P 内部通信连接,正式结成集群。
3. 绝对健康检查(每 1 秒执行 PING)
-
运行机制: 哨兵每 1 秒向它已知的所有节点 (Master、所有 Slaves、所有其他 Sentinel)发送
PING心跳检测。 -
核心价值: 监控整个拓扑网络中每一个成员的死活。一旦某个节点在限定时间内没有回复正常的
PONG,就会立刻触发状态判定。
三、 状态判定:主观下线与客观下线
故障转移的前提是准确判断 Master 是否真的挂了。哨兵引入了两级下线机制,有效避免了因单个哨兵网络隔离或临时抖动引发的集群误判。
1. 主观下线(Subjective Down, SDOWN)
当某个哨兵节点向 Master 发送 PING 命令后,如果在配置的 down-after-milliseconds 时间内没有收到有效的 PONG 回复,该哨兵就会在本地单方面(主观地)认为该 Master 已经不可用,将其标记为 SDOWN。
2. 客观下线(Objective Down, ODOWN)
核心: 哨兵集群在正常运行时是没有 Leader(领导者) 的。只有当 Master 被标记为"客观下线"时,才会触发 Leader 选举。
当某个哨兵将 Master 标记为 SDOWN 后,它会向其他哨兵节点发送 sentinel is-master-down-by-addr 命令,询问其他哨兵对该 Master 的看法。
当赞成下线的哨兵数量 达到或超过配置文件中设定的法定人数(Quorum)时,Master 就会被正式标记为客观下线(ODOWN)。
四、 核心工作流:从选举到故障转移
一旦 Master 被判定为客观下线,哨兵集群将按下述两阶段严密执行故障转移:
客户端 (Pub/Sub) 其余从节点 新主节点 哨兵A (Leader) 哨兵B 哨兵A 哨兵集群 客户端 (Pub/Sub) 其余从节点 新主节点 哨兵A (Leader) 哨兵B 哨兵A 哨兵集群 #mermaid-svg-R674ByOuLaGjQx5K{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-R674ByOuLaGjQx5K .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-R674ByOuLaGjQx5K .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-R674ByOuLaGjQx5K .error-icon{fill:#552222;}#mermaid-svg-R674ByOuLaGjQx5K .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-R674ByOuLaGjQx5K .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-R674ByOuLaGjQx5K .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-R674ByOuLaGjQx5K .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-R674ByOuLaGjQx5K .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-R674ByOuLaGjQx5K .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-R674ByOuLaGjQx5K .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-R674ByOuLaGjQx5K .marker{fill:#333333;stroke:#333333;}#mermaid-svg-R674ByOuLaGjQx5K .marker.cross{stroke:#333333;}#mermaid-svg-R674ByOuLaGjQx5K svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-R674ByOuLaGjQx5K p{margin:0;}#mermaid-svg-R674ByOuLaGjQx5K .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-R674ByOuLaGjQx5K text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-R674ByOuLaGjQx5K .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-R674ByOuLaGjQx5K .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-R674ByOuLaGjQx5K .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-R674ByOuLaGjQx5K .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-R674ByOuLaGjQx5K #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-R674ByOuLaGjQx5K .sequenceNumber{fill:white;}#mermaid-svg-R674ByOuLaGjQx5K #sequencenumber{fill:#333;}#mermaid-svg-R674ByOuLaGjQx5K #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-R674ByOuLaGjQx5K .messageText{fill:#333;stroke:none;}#mermaid-svg-R674ByOuLaGjQx5K .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-R674ByOuLaGjQx5K .labelText,#mermaid-svg-R674ByOuLaGjQx5K .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-R674ByOuLaGjQx5K .loopText,#mermaid-svg-R674ByOuLaGjQx5K .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-R674ByOuLaGjQx5K .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-R674ByOuLaGjQx5K .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-R674ByOuLaGjQx5K .noteText,#mermaid-svg-R674ByOuLaGjQx5K .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-R674ByOuLaGjQx5K .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-R674ByOuLaGjQx5K .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-R674ByOuLaGjQx5K .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-R674ByOuLaGjQx5K .actorPopupMenu{position:absolute;}#mermaid-svg-R674ByOuLaGjQx5K .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-R674ByOuLaGjQx5K .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-R674ByOuLaGjQx5K .actor-man circle,#mermaid-svg-R674ByOuLaGjQx5K line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-R674ByOuLaGjQx5K :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 第一阶段:哨兵 Leader 选举 (Raft 变种) 票数 >= 哨兵数/2 + 1 且 >= Quorum 当选为 Leader 第二阶段:由 Leader 执行 Failover 1. 过滤不健康/断连的 Slave 2. 严格按算法挑选出最优 Slave is-master-down-by-addr (拉票: 投我一票) 1 投票同意 (先到先得原则) 2 发送 slaveof no one (晋升为主) 3 发送 slaveof 新主IP (重定向主从关系) 4 发布 switch-master 消息 (重定向连接) 5
1. 第一阶段:哨兵领导者(Leader)选举
为什么需要选举? 并不是所有哨兵都能去操作 Redis 节点。为了防止多个哨兵同时指挥导致拓扑冲突,必须选出一个 Leader 来全权执行故障转移。
基于 Raft 一致性协议的变种,选举流程如下:
-
每个发现 Master 客观下线的哨兵,都会尝试让自己成为 Leader。它会向其他哨兵发送
sentinel is-master-down-by-addr命令,要求其他哨兵为自己投票(拉票)。 -
先到先得: 收到命令的哨兵,如果在此纪元(Epoch)内还没有投过票,就会同意该请求,否则拒绝。
-
过半兼法定原则: 某个哨兵获得的票数必须同时满足以下两个条件,才能成功当选 Leader:
-
票数 ≥ Quorum \ge \text{Quorum} ≥Quorum(达到配置的法定人数)
-
票数 ≥ 总哨兵数 / 2 + 1 \ge \text{总哨兵数} / 2 + 1 ≥总哨兵数/2+1(严格过半原则,防止集群脑裂)
-
2. 第二阶段:新主节点挑选算法(从机上位)
注意: 挑选新主有着极其严密的打分过滤算法,绝对不是随机推举!
哨兵 Leader 会对所有存活的从节点(Slaves)进行严格筛选,按以下顺序依次对比,只要在某一步拉开差距,就直接胜出:
| 比较顺序 | 筛选/对比规则 | 说明 |
|---|---|---|
| Step 1 | 过滤不健康节点 | 过滤掉已经下线、断线、或者最近与老 Master 断开连接时间过长(超过 down-after-milliseconds * 10)的从节点,保证新主本身健康。 |
| Step 2 | 比较 slave-priority(优先级) |
查看 redis.conf 中的配置,数值越小优先级越高。若某 Slave 的优先级配置为 0,则代表永远不会被提升为主。 |
| Step 3 | 比较 replication offset(复制偏移量) |
比较谁从老 Master 那里同步的数据最全。偏移量越大(说明数据最完整)者优先上位。 |
| Step 4 | 比较 runid(运行ID) |
如果以上全部相同,则比较从节点的启动 runid,字典序最小的节点优先上位。 |
3. 第三阶段:身份转换与拓扑重构
-
老从变新主: 选出最优 Slave 后,Leader 向其发送
slaveof no one(5.0之后为replicaof no one)命令,使其独立并晋升为新 Master。 -
其余从改嫁: Leader 向其他所有 Slave 发送
slaveof <新Master IP> <新Master Port>命令,让他们认新 Master 为老大,开始从新主同步数据。 -
老主变从机: 此时,若那个故障的老 Master 修复重新上线了,哨兵会捕获到它,并向其发送
slaveof命令,强制将其降级为新 Master 的从节点。
4. 第四阶段:客户端重定向(配置中心功能)
哨兵集群通过 Redis 内部的 发布/订阅(Pub/Sub)机制 ,向客户端(Jedis / Lettuce 等)广播 +switch-master 事件。客户端收到通知后,会自动断开与旧 Master 的连接,重新向新 Master 的 IP 建立连接,实现业务无缝切换。
五、 哨兵模式的痛点:为什么不能保证数据零丢失?
哨兵模式实现了高可用,但由于其底层的分布式架构设计,它并不能保证数据 100% 不丢失。原因主要有以下三点:
-
异步复制导致的数据丢失(Replication Lag):
Redis Master 到 Slave 的数据同步是异步的。在 Master 突发宕机前,可能有一部分刚刚写入的数据还没来得及同步到 Slave。由于 Slave 被强制晋升,这部分未同步的数据就彻底丢失了。
-
故障发现与转移期间的写丢失:
从 Master 真正宕机,到哨兵经历
主观下线 -> 客观下线 -> 选举 Leader -> 选出新主 -> 通知客户端,整个 Failover 流程需要花费数秒甚至十几秒。在这段权力真空期内,客户端如果继续向旧 Master 发送写请求,这部分数据将会失败或丢失。 -
网络分区造成的"脑裂"问题:
如果因为网络故障,Master 与哨兵及其他 Slave 隔离了,但与部分客户端还能连通。哨兵集群误以为 Master 挂了,在另一个网络分区里选出了新 Master。此时集群中同时存在两个 Master 。当网络恢复后,旧 Master 会被降级为从机,它在网络分区期间接收的独立写数据,会被新 Master 的同步数据彻底覆盖清空。