Raft
Raft 用选举和日志复制维护确定的状态机执行顺序。本文讲解任期、提交、线性读取、快照和成员变更。
1. 面试核心
先记住这几条,再读正文- 任期与投票。 节点每个 term 最多投一票,currentTerm 与 votedFor 需持久化。
- 日志新旧比较。 日志新旧先比较最后条目的 term,再比较 index。卡 · 角色与任期
- 提交规则。 Leader 仅按多数副本数直接提交当前 term 条目。卡 · 当前任期与旧任期条目
- 线性一致读。 线性读需确认领导权,并等待状态机应用到安全读取位置。卡 · 读取屏障
- 成员变更。 joint consensus 同时要求新旧配置的多数,learner 追赶时不计票。卡 · Learner 与多数门槛
2. 角色与任期
Follower 响应复制和投票请求。Candidate 在选举超时后发起新任期。Leader 接收命令、复制日志并发送心跳。
节点收到更大 term 后更新任期,并退回 Follower。同任期内只允许一票,避免两个候选者各获多数。
2.1 持久状态与故障模型
currentTerm、votedFor 和日志需在响应相关 RPC 前持久化。节点若重启后忘记投票,就会在同任期重复投票。副本若确认后丢失日志,也会破坏恢复前提。
Raft 处理崩溃、重启、延迟、丢包和分区。协议假设节点按规则执行。恶意伪造投票和日志属于拜占庭故障,超出普通 Raft 的保证。
固定配置下,2f+1 个投票节点可容忍 f 个不可用。继续推进还需多数节点互通并获得足够稳定的选举时机。
3. 领导者选举
Follower 选举超时后增加 term,给自己投票并请求其他票。候选者取得多数后成为 Leader。随机超时降低同时竞选的机会。
投票先比较最后日志条目的 term。term 相同再比较 index。较短日志若最后 term 更大,仍算更新。
选举超时应明显长于正常通信耗时。延迟持续过大时,频繁竞选会降低可用性。安全规则仍需保持。
4. 日志复制
日志条目包含 index、term 和命令。AppendEntries 携带前一个条目的 index 与 term。
Follower 检查前缀是否匹配。失败时 Leader 回退 nextIndex,重新寻找共同前缀。相同 index 的 term 冲突时,删除冲突条目及其后缀。已匹配的日志继续保留。
日志匹配性质保证相同位置与任期对应同一前缀。
5. 提交
Leader 跟踪各副本的持久日志位置。当前 term 的条目复制到多数后,可推进 commitIndex。状态机按 index 应用,再返回执行结果。
Follower 根据 Leader 的提交位置推进本地提交。commitIndex 表示已知可提交位置,lastApplied 表示实际应用位置。
5.1 当前任期与旧任期条目
旧 term 条目即使已复制到多数,也不能仅据此直接提交。其他节点可拥有更高任期的不同后缀,并在后续选举中胜出。
Leader 应先让一条当前 term 条目获得多数。它可是一条业务命令,也可以是 no-op。该条目提交后,之前的日志前缀随之提交。
选举的新旧比较、日志匹配和当前任期提交共同保证安全。多数交集本身只保证有共同节点。共同节点仍按日志新旧规则决定投票。
6. 客户端重试
命令已提交但响应丢失时,客户端会重试。日志中的第二条命令仍是一次独立输入。
状态机可保存 client ID、请求序号和结果。重复请求返回原结果。外部邮件或 HTTP 操作还需独立幂等,具体恢复见分布式流程恢复。
7. 线性一致读
Leader 失联后,其他节点可以选出新 Leader。旧 Leader 在获知更高任期前仍会自认有效。本地直接读会返回过期状态。
7.1 读取屏障
新 Leader 先提交当前 term 的条目,确认已提交前缀。论文使用启动任期时的 no-op。
一次安全读取还需确认当前领导权。ReadIndex 类流程与多数派交互,并得到安全读位置。状态机等待 lastApplied 达到该位置后,再执行读取。
Follower 也可向 Leader 获取安全读位置,等待本地应用后读取。只看自己的 term 或历史 commitIndex,缺少本次读取的确认。
租约读可减少往返,但安全性依赖时钟与租约假设。
8. 快照
快照保存已应用状态与最后包含的 index、term。已压缩的日志前缀由快照代替。过于落后的 Follower 安装快照,再复制后续日志。
快照还需包含生效的成员配置。状态与位置应一致且可持久恢复。共识副本之外的历史备份仍用于逻辑损坏恢复。
9. 成员变更
直接替换成员列表会让新旧多数互不相交。joint consensus 在过渡期同时取得两套配置的多数。之后再提交最终新配置。
部分实现采用经过证明的单成员变更协议。它与联合配置是不同实现路径,应按具体协议操作。
9.1 Learner 与多数门槛
三投票节点需要两票,四投票节点需要三票。若已有一个节点故障,再加入未就绪的投票节点,就只剩两票可用。
etcd 3.6 支持以 learner 加入新节点。learner 接收日志,但不参与投票,也不提高多数门槛。追平并满足提升检查后,再转成投票成员。
成员变更通过现有共识完成。丢失多数后,普通加节点操作无法恢复法定票数。
10. 常见问题
10.1 三节点集群的故障余量
三节点失去两台后,剩余一台无法取得多数。线性写入停止,以避免两个分区各自推进。允许陈旧读的接口可以另行提供服务。
10.2 心跳与空 AppendEntries
论文中的心跳使用空 AppendEntries。它仍携带任期、前缀和提交信息。实现可以优化消息格式,但需要维持相应安全条件。
11 · 面试题卡片
11.1 Raft 如何保证新 Leader 包含已经提交的日志
出现公司:字节跳动
考察重点
- term、多数投票和候选人日志新旧比较。
- AppendEntries 前缀匹配与冲突修复。
- 当前 term 提交规则、读取与成员变更。
参考回答
新 Leader 保留已提交日志,依赖选举限制和当前任期提交规则。
每个节点同任期只投一票。候选日志先比较最后 term,再比较 index。日志匹配保证共有条目前缀一致。
当前 term 条目由多数持久化后才直接提交。结合这些性质,可证明后续 Leader 都包含该前缀。旧 term 条目随新任期条目间接提交。仅说明两个多数相交,还不足以完成证明。
11.2 旧任期日志已经复制到多数节点,Leader 为什么还要提交当前任期条目?
出现公司:暂无已核验的公开来源(本站自测)
考察重点:选举的新旧规则与间接提交。
相关内容:当前任期与旧任期条目。
参考回答
旧任期条目的多数副本数不足以直接证明安全。
后续候选者仍可凭更高任期后缀通过新旧比较。当前任期条目得到多数后,协议才能保证后续 Leader 保留它。此前日志随该条目一起提交。
11.3 Raft Leader 执行线性一致读时,需要哪些确认?
出现公司:暂无已核验的公开来源(本站自测)
考察重点:当前任期提交、领导权与应用进度。
相关内容:读取屏障。
参考回答
Leader 需确认已提交前缀、当前领导权和状态机进度。
新任期先提交当前 term 条目,例如 no-op。读取再经多数派确认得到安全位置。lastApplied 达到该位置后,才读取本地状态。租约优化还需额外时钟假设。
11.4 三节点集群已有一台故障,为什么新增投票节点可能使集群停写?
出现公司:暂无已核验的公开来源(本站自测)
考察重点:成员数量、多数门槛与 learner。
相关内容:Learner 与多数门槛。
参考回答
成员数变为四个后,多数门槛从两票升到三票。
新节点尚未就绪时,只有原来的两票可用。learner 追赶期间不计票,因此保留原门槛。追平后按协议提升为投票成员。