跳到主要内容

Raft 选举与日志复制

Raft 让多数节点对复制日志的顺序达成共识,再由每个节点按相同顺序驱动状态机。它保证的是满足协议的多数派日志安全,不能让失去多数节点的集群继续接受线性一致写入。

1. 节点在三个角色间转换

  • Follower:响应 Leader 与 Candidate 的 RPC。
  • Candidate:选举超时后发起新任期并请求投票。
  • Leader:接收客户端命令,复制日志并发送心跳。

term 是单调增加的逻辑任期。节点看到更大 term 时更新本地任期并退回 follower,旧 leader 的消息因此不能覆盖新任期状态。

2. 随机选举超时减少平票

Follower 在一段随机时间内没有收到有效 Leader 通信,就增加 term、给自己投票,并向其他节点请求票。获得多数票的 Candidate 成为 Leader。

每个节点在一个 term 最多投一票。候选人的日志还必须至少和投票者一样新,比较最后日志的 term,再比较 index。这个限制保证已经提交的日志不会被缺少它的候选人轻易取代。

平票时无人获得多数,节点在新的随机超时后进入下一 term。随机范围要显著大于正常广播和网络抖动,否则集群会频繁无效选举。

3. Leader 为日志建立全局顺序

客户端命令先追加到 Leader 日志,条目包含 index、term 和命令。Leader 使用 AppendEntries 把新条目复制到 followers,并携带前一条日志的 index 与 term。

Follower 只有当前缀匹配时才追加;不匹配则拒绝。Leader 回退 nextIndex,找到共同前缀后删除 follower 冲突后缀并重新复制,使日志最终一致。

4. 多数复制后才推进 commit index

Leader 跟踪每个 follower 已复制位置。当前 term 的某条日志存储在多数节点后,Leader 可以推进 commit index,把条目按顺序应用到状态机并响应客户端。

Raft 对以前 term 条目的提交有一条重要限制:Leader 不会仅根据副本数直接把旧 term 条目标记提交;它先提交当前 term 的条目,之前的前缀随之成为已提交。这避免特定选举历史下错误覆盖。

Follower 从 Leader 的 commit index 得知哪些条目可以应用。应用状态机必须按 index 顺序且确定性执行。

5. 客户端重试需要请求去重

Leader 可能已经提交命令,但响应在网络中丢失;客户端随后向新 Leader 重试。Raft 保证日志顺序,不自动理解两条命令属于同一个业务请求。

状态机可以保存 client ID 与单调 request sequence,重复命令返回之前结果。涉及外部数据库、邮件或 HTTP 副作用时,它们不在 Raft 状态机内,仍需独立幂等协议。

6. 线性一致读取也需要协议

Leader 刚刚与多数派失联时,可能尚未知道新 Leader 已产生。若直接读取本地状态,会返回旧值。

常见做法是先通过心跳或 ReadIndex 确认自己仍是当前多数派认可的 Leader,再读取已应用到 commit index 的状态;基于租约的读取依赖时钟和租约假设。Follower 读取通常是陈旧读,除非经过额外确认。

7. 日志压缩使用 snapshot

日志持续增长后,节点把已应用前缀压缩为状态机 snapshot,记录最后包含的 index 与 term。落后太多的 follower 可以安装 snapshot,再继续复制后续日志。

生成 snapshot 时要与状态机状态一致,传输支持校验和断点处理。不能删除仍有恢复或成员需要的日志前缀,也要备份集群之外的数据灾难恢复路径。

8. 成员变更避免两个多数派定义

直接从旧配置切到新配置,可能在过渡期出现两个不相交的多数集合。Raft 使用 joint consensus 或经过证明的单节点变更流程,让过渡决策同时满足旧、新配置要求。

不要一次替换多数成员,也不要把节点列表当普通配置文件手工编辑。新增节点先追赶日志,再进入投票集合;移除节点同样通过共识日志完成。

9. 常见问题

9.1 三节点集群宕机两台后为什么不能写

剩余一台无法取得多数,也无法确认自己是不是与另两台隔离的旧 Leader。继续接受写入会允许两个分区各自提交,破坏单一日志。它可以提供明确允许的陈旧读,但不能承诺线性一致写。

9.2 心跳是否只是空的 AppendEntries

通常是。它携带 term、前缀与 commit 信息,用于维持领导权、发现日志差异并推进 follower 提交,不只是“进程还活着”的探测包。

10. 面试题

10.1 Raft 如何保证新 Leader 包含已经提交的日志

出现公司:字节跳动

考察重点

  • term、多数投票和候选人日志新旧比较。
  • AppendEntries 前缀匹配与冲突修复。
  • 当前 term 提交规则、读取与成员变更。

相关内容:第 1 节“节点在三个角色间转换”至第 8 节“成员变更避免两个多数派定义”。

参考回答

每个 term 一个节点最多投一票,候选人只有最后日志 term 更大,或 term 相同且 index 不小于投票者时,才算至少一样新。已经提交的条目存在于多数节点,任何获得新多数票的 Candidate 都必须与这个多数相交,并通过日志新旧限制包含相应已提交前缀。

新 Leader 用 AppendEntries 的前一 index 与 term 检查前缀,回退位置并覆盖 follower 的未提交冲突后缀。Leader 对当前 term 条目复制到多数后推进 commit,旧 term 条目随当前条目一起提交。线性一致读还要确认当前领导权,成员变更则通过 joint consensus 或安全的逐节点流程,不能直接替换多数。