《深入理解 Nacos 集群与 Raft 协议》系列
大家好,我是G探险者!
在第 1 篇中,我们知道 Nacos 集群依赖 Raft 协议来保证节点间的一致性。而 Raft 中一个非常重要的机制就是:选主机制(Leader Election)。
那么问题来了:
- 为什么 Raft 要有 Leader?
- 什么时候触发选主?
- 如果多个节点同时选主会不会冲突?
- 怎么才能选出一个合法、安全的 Leader?
本篇将一一揭晓。
一、Raft 为什么必须要 Leader?
Raft 的核心设计理念就是:
通过一个中心节点(Leader)来驱动整个集群的数据写入与同步流程。
Leader 的职责包括:
- 统一接收客户端的写请求
- 向所有 Follower 同步日志(AppendEntries)
- 管理当前集群的"任期 term"和"提交位置 commitIndex"
Follower 节点:
- 不对外提供写能力
- 被动接收 Leader 的同步请求
没有 Leader,Raft 集群无法处理任何写操作。
二、选主的三种角色状态
每个 Raft 节点在任意时刻都处于以下三种状态之一:
状态 | 含义 |
---|---|
Follower | 普通从节点,等待接收心跳 |
Candidate | 候选人,参与竞选 Leader |
Leader | 当前任期的领导者 |
状态之间转换规则如下:
text
启动 → Follower
↓(超时未收到心跳)
Candidate(发起投票)
↓(获半数投票)
Leader(发起日志同步)
三、什么时候触发选主?
1. 初始启动时
- 所有节点都是 Follower
- 没有 Leader 心跳 → 触发选举
2. 原 Leader 崩溃 / 网络分区
- Follower 超时没收到心跳
- 自动转为 Candidate 发起选举
3. 任期冲突 / 分区恢复
- 任期发现比自己新 → 立即降级为 Follower
这种设计非常自动化,不依赖人工干预。
四、选主的完整流程(投票机制)
选主由 Candidate 节点发起,流程如下:
-
任期加一:term 自增,标志新一轮投票
-
投票给自己
-
发送 RequestVote 请求 给其他节点,包含:
- 当前任期 term
- 自己最后日志的 index 与 term
-
其他节点判断是否投票:
- 是否已经投过票?(一个任期只能投一次)
- 候选人的日志是否"更新"?(详见第 3 篇)
-
如果获得超过半数投票 → 成为 Leader
-
否则进入下一轮等待或失败重试
五、多个节点同时选主怎么办?
有可能同时多个节点触发超时,一起成为 Candidate,这时会出现"互相抢票"的情况。
比如:节点 A、B、C 同时发起选举,但各自只拿到一票(自己的一票),都不够过半。
这种情况下,Raft 设计了 随机超时时间机制:
- 每个节点的 election timeout 是随机的(如 150ms~300ms)
- 下次谁先超时,谁先发起下一轮选举,最终一定会有人赢得选举
所以:
Raft 通过"延迟退避+随机超时"避免了脑裂和长期冲突。
六、如何保证新 Leader 是合法的?
新 Leader 不仅要获得多数节点的选票,还要通过 日志完整性校验(见第 3 篇):
- Follower 只会投票给"日志最完整"的节点
- 拥有未提交日志的节点不能当选 Leader
这样可以防止"老节点"丢失已提交数据后成为 Leader,避免数据丢失。
七、Nacos 中的选主体现在哪?
在 Nacos 中你可能会看到类似日志:
text
[RAFT] Leader is null, no available leader in current term
[RAFT] Start vote, become candidate...
[RAFT] Vote granted from NodeB
[RAFT] Become leader of term 5
也可以通过调用接口或访问集群页面查看当前哪个节点是 Leader。
总结
Raft 的"选主机制"是整个一致性架构的核心:
- 每轮只能选出一个 Leader,统一处理写请求
- 选主通过 RequestVote 实现,结合日志校验、随机超时
- 多节点同时竞选不会脑裂,最终一定选出唯一 Leader
下一篇,我们将重点解析:
💡 Raft 是如何通过"日志对比"防止不合法节点当选 Leader?
敬请期待第 3 篇!