Raft 如何保证日志一致性和安全性?为什么已提交的日志不会丢?
简化版
Raft 通过任期、日志匹配、投票限制和多数派提交保证安全性。日志条目用 index 和 term 标识,Follower 只有在前一条日志匹配时才追加新日志;如果发生冲突,会删除冲突位置之后的日志并跟随 Leader。选举时,节点只投给日志至少和自己一样新的候选人,因此新 Leader 不会缺少已提交日志。已提交日志存在于多数派,新 Leader 也必须获得多数票,两者必有交集,所以已提交日志不会丢。
详细版
Raft 的安全性重点包括三条。
第一,日志匹配原则:如果两个日志条目拥有相同 index 和 term,那么它们保存的命令相同,并且它们之前的日志也相同。Leader 通过 AppendEntries 中的 prevLogIndex 和 prevLogTerm 来检查这个条件。
第二,Leader 完整性:如果某条日志已经在某个任期提交,那么后续任期的 Leader 一定包含这条日志。原因是提交需要多数派,而新 Leader 当选也需要多数票,多数派之间有交集;投票规则又要求候选人日志不能落后。
第三,提交规则:Leader 只能直接提交当前任期、并被多数复制的日志;旧任期日志通常通过当前任期日志间接提交。这样可以避免旧 Leader 的未完成日志在新任期被错误认为已提交。
完整版教学
一、Raft 安全性到底在保护什么
Raft 保护的不是“所有节点时时刻刻完全一样”,因为分布式系统里节点可能延迟、宕机、重启,Follower 落后是常态。Raft 真正保护的是:只要某条日志被提交并应用到状态机,以后所有正常节点最终都会在同一位置应用同一条日志,不会被覆盖,不会变成另一个命令。
这就是状态机安全性。如果节点 A 在 index=10 应用了“扣库存”,节点 B 就不能在 index=10 应用“发优惠券”。只要日志位置和命令不一致,状态机执行顺序就会分裂,系统就不再可信。
二、日志匹配原则怎么保证前缀一致
Raft 的每条日志都有 index 和 term。AppendEntries 请求会携带 prevLogIndex 和 prevLogTerm,Follower 只有发现自己在这个位置上也有相同 term,才接受后续日志。否则它会拒绝,Leader 逐步回退 nextIndex,直到找到双方共同的日志前缀。
找到共同前缀后,如果 Follower 在后面有冲突日志,就删除冲突位置及其之后的日志,再追加 Leader 发来的日志。这听起来像 Leader 很强势,但它是安全的,因为 Raft 保证能成为 Leader 的节点不会缺少已提交日志。因此 Leader 覆盖的只能是未提交或不该继续保留的分叉日志,不会覆盖已经提交的历史。
三、投票限制为什么重要
选举时,投票节点不能只看候选人任期号,还要看候选人的日志是否足够新。Raft 用最后一条日志的 term 和 index 比较新旧:term 更大的日志更新;term 相同则 index 更大的日志更新。只有候选人日志至少和投票者一样新,投票者才会投票。
这条规则防止落后节点成为 Leader。假设某条日志已经提交,它必然存在于多数节点上。后续候选人要当选,也要拿到多数票。两个多数派有交集,交集节点持有已提交日志;如果候选人没有这条日志,它的日志就落后,交集节点不会投票给它。这样新 Leader 必然包含已提交日志。
四、为什么已提交日志不会丢
“提交”在 Raft 中不是 Leader 自己写了就算,而是被多数节点复制后才算。多数派提交带来一个强性质:任何未来 Leader 的选举多数派,都会和这个提交多数派相交。交集节点就像历史的锚点,把已提交日志带入后续任期。
再配合投票限制,候选人如果缺少已提交日志,就拿不到交集节点的票,因此无法成为 Leader。既然所有未来 Leader 都包含已提交日志,Leader 又是日志复制的源头,那么这条已提交日志只会被继续传播,不会被删除或替换。
五、当前任期提交规则的细节
Raft 还有一个容易被忽略的细节:Leader 通常只根据“当前任期日志被多数复制”来推进 commitIndex。旧任期日志即使看起来被多数复制,也不能单独用来证明安全提交;它们会在当前任期的新日志提交后,作为前缀被间接提交。
这个设计是为了处理复杂的 Leader 切换场景。旧任期日志可能在不同节点上有不同传播状态,如果新 Leader 直接提交旧任期日志,可能和后续选举安全性产生边界问题。当前任期日志被多数复制后,说明当前 Leader 的日志前缀已经被多数确认,此时前面的旧日志也安全了。
六、面试追问与工程边界
常见追问是 Follower 被覆盖日志会不会丢数据。回答要区分已提交和未提交:已提交日志不会被合法 Leader 覆盖;未提交日志只是曾经被某个 Leader 尝试复制过,并没有对客户端形成安全承诺,Leader 切换后被覆盖是允许的。客户端只有在日志提交后收到成功,才应该认为写入完成。
另一个追问是读请求如何安全。即使日志安全,读也可能被旧 Leader 影响。强一致读需要 Leader 确认自己仍与多数派保持联系,例如 ReadIndex、Leader lease 或提交 no-op。否则旧 Leader 在网络分区中本地读,可能读到陈旧状态。
七、常见误区与追问
这道题不能只背概念,要把「Raft 安全性」放回真实分布式系统里解释:参与方是谁、状态怎么流转、失败后怎么恢复,以及它在一致性、性能、可用性之间做了什么取舍。
| 回答层次 | 要讲清的内容 | 容易漏掉的边界 |
|---|---|---|
| 核心结论 | Raft 安全性依赖选举限制、日志匹配、Leader 完整性和提交规则,保证已提交日志不会被覆盖 | 不要停在名词解释 |
| 流程机制 | 投票时比较 lastLogTerm/index -> 旧日志候选人被拒票 -> 新 Leader 包含已提交日志 -> AppendEntries 做日志匹配 -> 冲突日志被覆盖但已提交日志保留 | 说明触发方、存储方、确认点和兜底 |
| 工程取舍 | 候选人日志比多数派旧时拿不到投票,因此包含已提交日志的节点更可能成为 Leader | 一致性协议用延迟和可用性换确定顺序,不能只背 Paxos/Raft 名词 |
Raft 安全性 面试拆解:
1. 投票时比较 lastLogTerm/index
2. 旧日志候选人被拒票
3. 新 Leader 包含已提交日志
4. AppendEntries 做日志匹配
5. 冲突日志被覆盖但已提交日志保留
记忆钩子:先说明故障模型和多数派,再拆选主、日志复制、提交、恢复和安全性边界;回答时要紧扣「Raft 安全性」这道题,不要把相邻概念混成一段泛泛的分布式套话。
- 误区:Raft 只靠多数派就能保证所有安全性。 多数派是基础,还要配合日志新旧比较和提交规则。
- 误区:新 Leader 可以任意覆盖旧日志。 只能覆盖未提交冲突日志,不能覆盖已提交日志。
- 误区:term 只用于选举。 term 也参与日志新旧判断和消息过期识别。
- 追问:Leader Completeness 是什么? 某任期提交的日志会出现在后续任期所有合法 Leader 中。
- 追问:为什么候选人日志旧就拒票? 防止缺少已提交日志的节点成为 Leader。
- 追问:如何处理日志冲突? Follower 根据 prevLogIndex/Term 校验,不匹配则回退并覆盖未提交冲突日志。
八、加强记忆
Raft 安全性可以记成“日志匹配修分叉,投票限制防落后,多数交集保历史”。AppendEntries 用 prevLogIndex 和 prevLogTerm 找共同前缀;投票只给日志足够新的候选人;提交必须经过多数。已提交日志在一个多数派里,新 Leader 也来自多数派,交集节点会阻止缺日志候选人当选,因此已提交日志不会丢。