分布式共识协议 Raft
校招考察频度:☆ 中频(重点常考·大厂分水岭) 一句话速记:Raft 将复杂的分布式共识拆解为三大子问题(Leader 选举、日志复制、安全性);通过随机超时破除选举分票,依靠多数派原则(过半 N/2+1 确认)保障可用,以“日志更全者方可当选”杜绝已提交数据丢失,用单调任期 Term 天然平息脑裂。
在分布式系统面试中,Paxos 算法因为晦涩难懂几乎很少被直接考源码,而Raft 作为工业界事实上的统一标准(etcd、Consul、TiKV、Kafka KRaft 全在用),是面试官考察分布式一致性的最爱。
一、0 基础直观理解:班级记账与选班长模型
如果把一个分布式集群比作**“一个共同记账的班级”**:
| Raft 概念 | 生活化比喻 | 现实运行逻辑 |
|---|---|---|
| Leader(领导者) | 正班长 | 全班唯一的记账人,负责接纳外界所有转账指令,并命令其他同学同步抄写。 |
| Follower(跟随者) | 普通同学 | 平时只安静听班长指挥并抄写账本,定时接收班长的“我还在工作”的心跳。 |
| Candidate(候选人) | 竞选班长 | 当普通同学很久没收到班长消息(超时)时,怀疑班长病退,举手自荐竞选新班长并拉票。 |
| Term(任期) | 学年届数(第 1 届、第 2 届…) | 单调递增的逻辑时钟。任何节点只要看到比自己高的 Term,立刻自愿听从对方。 |
| 多数派(Quorum) | 过半数同学举手表决(N/2 + 1) | 只要超过半数同意,决议就具有不可推翻的全局法律效力。 |
二、三大子问题之一:Leader 选举与随机超时
Raft 节点状态机流转与选举时序
1. 为什么选举超时时间(Election Timeout)必须是随机的?
- 痛点:选票瓜分(Split Vote)死锁:如果所有节点的超时时间都是固定的 200ms,一旦 Leader 宕机,所有 Follower 将在同一微秒同时发起竞选,各自给自己投一票,最终谁也无法获得过半票数,选举陷入无限僵局!
- 解决方案:随机化(Randomized Timeout):将每个节点的超时时间打散在 150ms ~ 300ms 之间。跑得最快的节点会率先超时并向其他人发起拉票,在别人刚醒来时就已成功斩获过半选票,单次平滑收敛,零僵局。
三、三大子问题之二:日志复制与两阶段提交
Raft 的核心使命是维护所有节点上**日志复制状态机(Replicated State Machine)**的一致性:
┌─────────────────────────────────────────────────────────────┐
│ 1. 客户端发来写请求: Leader 将命令追加到本地 Log 末尾 (未提交)│
└──────────────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. 并发广播 AppendEntries: Leader 异步发给所有 Follower │
└──────────────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. 多数派落盘确认: 超过半数 (N/2 + 1) 节点写入成功后 │
│ Leader 正式将该日志标记为【已提交 (Committed)】 │
└──────────────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. 应用到状态机并响应客户端: 下一次心跳告知 Follower 一同提交 │
└─────────────────────────────────────────────────────────────┘- 过半即成功:5 节点的集群,只要 3 个节点写入成功即可提交,允许 2 个节点临时宕机或网络卡顿,可用性与强一致性兼备。
四、三大子问题之三:核心安全性(已提交日志绝不丢失)
为了杜绝“新当选的 Leader 肚子里没有最新提交的数据”,Raft 制定了两条绝对不可逾越的铁律:
1. 选举限制:日志不全者直接取消当选资格
当 Follower 收到候选人的拉票请求(RequestVote)时,不仅比任期,还必须比对最后一条日志的最新度(Last Log Index & Last Term):
- 只有候选人的日志**“至少与自己一样新(任期更高,或者任期相同但 index 编号更大)”**时,Follower 才会把票投给它。
- 数学证明:一条日志被提交,意味着它已经存在于“多数派”节点上;而任何新 Leader 要当选,也必须获得“多数派”的投票。两个多数派集合必定至少相交于 1 个共同节点!这个交集节点必定包含最新的已提交日志,并会直接一票否决那些落后的候选人。
2. 提交限制:Leader 绝不直接提交旧任期的日志
当前 Leader 只能通过计算“当前任期自己产生的日志”是否过半来推进 commitIndex,旧任期的残留日志只能顺带连带提交,防止极端的并发网络乱序覆盖历史数据。
五、脑裂(Split-Brain)如何优雅防范?
原始 5 节点集群发生网络分区:
[Node 1 (旧 Leader), Node 2] ❌ [Node 3, Node 4, Node 5]
(少数派分区: 2 节点) (多数派分区: 3 节点)- 少数派分区(2 节点):Node 1 试图写数据,但只有 2 票(未达到 3 票),所有的写入请求永远无法提交,处于阻塞等待。
- 多数派分区(3 节点):因收不到心跳,Node 3 率先超时,自增 Term 选出新 Leader。拥有 3 票过半优势,正常对外提供读写服务。
- 网络分区恢复时:旧 Leader(Node 1)收到了新 Leader(Node 3)发来的更高 Term 心跳,立刻心服口服地降级为 Follower,并用新 Leader 的日志截断并覆盖自己的未提交日志。整个集群平滑愈合,从数学上根本不可能出现双主双写的“脑裂”现象!
六、知识脉络关联网(Knowledge Highway)
- ⬅️ 底层日志机制:单机数据库是如何写 WAL 日志保障崩溃恢复的?参见 PostgreSQL WAL 与崩溃恢复。
- ➡️ 一致性模型深度:Raft 保证的是什么级别的一致性?参见 分布式一致性模型(线性一致 vs 顺序一致)。
- 🔄 工业级实战应用:RabbitMQ 如何用基于 Raft 的 Quorum 队列彻底根除镜像队列的脑裂问题?参见 存储机制与高可用集群。
七、校招高频面试追问
- Raft 集群的节点数为什么推荐是奇数(3 或 5 个),而不是偶数?
- 容错能力由公式决定: 个节点最多容忍 个节点故障。
- 3 节点集群能容忍 1 个故障(),4 节点集群也只能容忍 1 个故障(因为 4 节点过半需要 3 票,)。偶数节点不仅没有提升可用性,反而增加了一台服务器的成本与选举平票概率,纯属浪费。
- Leader 宕机恢复后,之前已写进本地但未提交的日志怎么处理?
- 新 Leader 当选后拥有绝对权威。旧 Leader 恢复并作为 Follower 加入后,新 Leader 会在其追加日志的 RPC 中检查一致性索引。如果发现 Follower 的末端日志与自己不符,会强行命令 Follower 截断并删除那些未提交的脏日志,以新 Leader 为准保持全局统一。