《深入理解 Nacos 集群与 Raft 协议》系列
大家好,我是G探险者!
在第 2 篇中,我们讲解了 Raft 的选主过程。但单靠"投票数过半"并不能完全保障新 Leader 的安全性。
为了防止数据丢失和不合法的 Leader 被选出,Raft 引入了关键的 日志对比机制(Log Comparison)。这一机制确保:
只有拥有"最新日志"的节点才能当选 Leader。
那么,"最新"是如何判断的?这个机制是如何工作的?又是如何保障数据一致性的?我们逐一解析。
一、为什么投票不能只看任期?
设想一个场景:
- 第 1 次提交时,A 是 Leader,ABC 同步成功
- A 崩溃后,CDE 成为候选人集群
- D 发起选主,但 D 的日志落后于之前提交的日志
如果这时 D 被选为 Leader,就会出现:
- D 发起的 AppendEntries 会让 C 删除与其不同的日志项
- 导致之前提交的日志被回滚,数据丢失!
这就是 "旧节点错误地成为 Leader" 导致的严重后果。
Raft 设计了日志对比机制来彻底避免这种情况。
二、日志对比的两个关键字段
当 Candidate 发起选举时,会在 RequestVote 请求中附带:
text
lastLogIndex:最后一条日志的索引
lastLogTerm:最后一条日志的任期号
Follower 收到投票请求时,会进行判断:
候选人的日志是否"更新"?
判断规则如下:
- 谁的 lastLogTerm 大,谁的日志更新
- 如果 lastLogTerm 相同,则看 lastLogIndex 谁大
只有候选人日志"更新"时,Follower 才会投票。
三、为什么这样判断就足够安全?
这种机制保证了:
- 所有当选的 Leader,日志一定不比大多数节点旧
- 从而不会覆盖大多数节点上已经存在的日志
换句话说,新 Leader 必须包含"前任 Leader 已提交的日志"。
这就实现了我们在第 1 篇中提到的:"每次提交的多数派之间必须有交集",从而实现日志递进一致性。
四、举个例子来理解日志对比
假设 5 个节点:A、B、C、D、E
- A 是 Leader,提交了 index=3 的日志
- 复制到了 ABC 节点
这时 A、B 宕机,C、D、E 存活。
- D 发起选举,日志只到 index=1(旧)
- C 拒绝投票,因为自己的 lastLogTerm/index 更新
→ D 选举失败,不能成为 Leader
接下来 C 发起选举,D、E 都投票 → 成功成为新 Leader
因为 C 拥有最完整的日志,才有资格成为 Leader。
五、Raft 如何保证最终一致性?
有了日志对比机制,Raft 能做到:
- 选主只选"最新"的节点,防止数据倒退
- 新 Leader 会用自己的日志覆盖旧节点的非一致日志
- AppendEntries 中包含前一个日志的索引和任期,作为对齐基准
最终,每个节点的日志都会逐步和 Leader 保持一致。
六、在 Nacos 中的体现
你可以从以下场景中感受到日志对比的存在:
- 节点日志落后,迟迟不能当选 Leader
- 选主期间频繁看到日志校验失败的投票日志
- 某节点刚启动,必须通过日志对齐后才成为稳定成员
这些机制背后,都是为了实现一致性的日志对比与对齐。
七、总结
Raft 的日志对比机制,是保障"选主安全性"和"日志最终一致性"的核心:
- 投票时检查候选人日志是否比自己新
- 防止日志回滚,避免脑裂与数据丢失
- 所有新 Leader 都必须有前任 Leader 的提交副本
下一篇我们将继续讲解:
💡 Raft 日志是如何复制的?提交策略、确认机制与幂等性保障
敬请期待第 4 篇!