共识算法与Raft独立面试题:选举、复制、线性读、成员变更与排障
本页只放面试标准回答、追问和精确原理跳转。基础推导见 Paxos、Raft、ZAB 与多数派,实现层和生产 Runbook 见 Raft 内部原理与生产治理。
一、共识基础
| 问题 | 标准回答 | 原理 |
|---|---|---|
| 什么是分布式共识 | 多个非拜占庭节点在宕机、分区、延迟和乱序下,对一串操作的顺序与提交达成一致,并按同序 Apply 到复制状态机。 | 共识对象 |
| 共识和复制有什么区别 | 复制只是把数据拷贝到多处;共识还规定谁能排序、什么条件算提交、冲突怎样恢复,保证已提交历史不分叉。 | 作用边界 |
| 共识和分布式事务有什么区别 | 共识保证一个复制状态机内部的日志顺序;分布式事务协调多个业务资源的原子性或补偿。一个事务协调器内部可用共识,但二者不是同一问题。 | 作用边界 |
| Safety 和 Liveness 区别 | Safety 是永远不提交两个冲突结果;Liveness 是网络和多数恢复后最终继续推进。分区时少数派牺牲写可用性保护 Safety。 | Safety/Liveness |
| Raft 处理拜占庭故障吗 | 经典 Raft 假设 Crash Fault,不处理节点恶意伪造 ACK、双签或篡改消息;拜占庭场景要研究 PBFT、HotStuff 等。 | 故障模型 |
| 多数派为什么有效 | 任意两个严格多数集合必有交集,后续选举有机会继承旧多数已知事实;但还必须配合日志新旧、持久化和提交规则。 | Quorum |
| N 个节点能容忍几个故障 | 2f+1 个 Voter 通常可容忍 f 个崩溃并保持多数;3/5/7 节点分别容忍 1/2/3 个。 | Quorum计算 |
| 为什么通常用奇数 Voter | 4 节点多数是 3,只容忍 1 个故障,与 3 节点相同却多一份复制成本;5 节点才提升到容忍 2 个。 | 奇偶节点 |
| 两个 Leader 是否一定违反 Raft | 不一定。网络延迟下可能短暂看到不同 Term 的节点都自认 Leader;安全关键是同一日志位置不能有两个冲突条目都合法提交。 | 网络分区 |
| 少数派旧 Leader 能继续写本地日志吗 | 可以本地 Append,但不能获得当前配置 Quorum,不能推进 Commit 或向客户端承诺成功;恢复后未提交尾部可被覆盖。 | 分区恢复Demo |
| 共识能保证 Exactly Once 吗 | 不能。命令可能已 Commit/Apply 但响应丢失;客户端必须使用稳定 requestId,状态机保存去重结果或通过 CAS/唯一约束确认。 | 客户端幂等 |
| 为什么状态机必须确定性 | 相同日志若在不同副本调用本地时间、随机数或外部 HTTP,结果仍会分叉。非确定值应在提议前写进命令。 | 确定性状态机 |
| CAP 与 Raft 是什么关系 | Raft 面对网络分区时通常要求多数才能提交,属于偏 CP 的协调基础;CAP 解释取舍,Raft 给出一种实现安全一致的协议。 | 网络分区 |
| Raft 五个安全性质是什么 | Election Safety、Leader Append-Only、Log Matching、Leader Completeness、State Machine Safety。 | 五个性质 |
| Leader Completeness 是什么 | 已提交条目必须存在于所有更高 Term 的合法 Leader 日志中,这是未来 Leader 不覆盖已提交历史的核心。 | 安全性质 |
二、Term、选举、PreVote 与 CheckQuorum
| 问题 | 标准回答 | 原理 |
|---|---|---|
| Term 是什么 | Term 是逻辑领导任期;RPC 携带 Term,节点看到更高 Term 通常更新并退回 Follower,低 Term 消息被拒绝。 | Term |
| 哪些状态必须持久化 | currentTerm、votedFor 和日志条目;它们必须在依赖响应前稳定存储,避免重启后同 Term 投两票或丢失已 ACK 日志。 | 状态分层 |
| CommitIndex 必须每次 fsync 吗 | Commit 可从 Leader 重新传播;条目、Term、Vote 才是关键持久事实。具体 WAL 可批量保存 Commit,但不能因此跳过日志/投票持久化。 | Ready契约 |
| 为什么选举超时要随机 | 降低多个 Follower 同时成为 Candidate、持续平票的概率;随机化改善活性,不是 Safety 的来源。 | 选举超时 |
| 选举超时怎么配置 | 要显著大于心跳,并覆盖正常网络 P99、fsync、GC 和调度停顿;过短制造选举风暴,过长增加故障恢复时间。 | 超时模型 |
| PreVote 是什么 | 节点先以未来 Term 询问自己能否获多数,不立即增加 currentTerm;预投票成功后才进入正式 RequestVote。 | PreVote |
| PreVote 解决什么问题 | 防止长期隔离节点反复增加 Term,重连后无意义地让稳定 Leader 退位;它不阻止合法选举。 | PreVote动机 |
| RequestVote 怎样判断日志新旧 | 先比较 LastLogTerm,较大者更新;Term 相同再比较 LastLogIndex,不能只看日志长度。 | 投票规则 |
| 为什么一个 Term 只能投一票 | 持久化 votedFor 让两个候选不能分别拿到相交多数;重复请求可再次投给同一候选。 | Vote持久化 |
| VoteResp 为什么必须先落盘 | 若先回复、后落盘,断电重启会忘记投票并给另一候选投票,破坏 Election Safety。 | Vote失败窗口 |
| CheckQuorum 做什么 | Leader 若一个 ElectionTimeout 内看不到多数 Voter 活跃会退回 Follower,让少数派旧 Leader 更快停止无效服务。 | CheckQuorum |
| CheckQuorum 是完美故障检测吗 | 不是,它只根据近期通信和超时判断;慢磁盘、GC、CPU 和非对称网络都可能造成误判。 | CheckQuorum边界 |
| Leader 上任为什么写空日志 | 当前 Term 空 Entry 提交后可确认旧前缀、建立本 Term 提交点,并释放等待当前 Term 提交的 ReadIndex。 | Leader No-op |
| 空日志不含业务数据还要 fsync 吗 | 要。它仍是普通 Raft Entry,参与 Term、Quorum 和提交安全,只是 Apply 时业务 Data 为空。 | Leader No-op |
| Leadership Transfer 怎样工作 | 先把目标 Voter 日志追平,暂停普通提议,再用 TimeoutNow 触发目标选举;目标不可达或超时则取消。 | 领导权转移 |
| 直接 Kill Leader 和 Transfer 区别 | Kill 依赖随机超时重新选举;Transfer 预先选择并追平目标,可缩短计划维护窗口,但仍需要 Quorum。 | 领导权转移 |
| 被移除节点为什么还能扰动集群 | 它可能仍持有旧配置和日志并发 Vote;PreVote/CheckQuorum 可减轻,但运维还应停止旧进程、撤销证书和隔离 Peer 地址。 | 移除节点 |
| Node ID 可以复用吗 | 不应随意复用。ID 关联投票权、Progress、消息和持久历史;旧磁盘与新空节点同 ID 会破坏身份唯一假设。 | Node ID |
三、日志复制、冲突回退和提交
| 问题 | 标准回答 | 原理 |
|---|---|---|
| AppendEntries 携带什么 | Term、LeaderId、PrevLogIndex、PrevLogTerm、Entries 和 LeaderCommit;空 Entries 也可作为心跳/提交传播。 | AppendEntries |
| 为什么日志身份是 Index+Term | 相同 Index 可能由不同任期 Leader 写入不同命令;Term 相同且 Index 相同才能作为同一日志点并推导前缀匹配。 | 日志身份 |
| PrevLogIndex/PrevLogTerm 做什么 | Follower 只有前置日志存在且 Term 相同才接收后续 Entries,保证 Log Matching Property。 | Append链路 |
| Follower 发现冲突怎么办 | 删除冲突点之后的未提交尾部并追加 Leader 历史;若冲突涉及已提交位置应视为严重实现/存储错误。 | 冲突回退 |
| nextIndex 和 matchIndex 区别 | Match 是已确认 Follower 拥有的最高 Index;Next 是下一次发送起点,可乐观推进或因 Reject 回退。 | Progress |
| Progress Probe 状态做什么 | 每轮有限探测日志匹配点,避免在 Next 不确定时持续发送大量错误 Append。 | Progress三态 |
| Progress Replicate 状态做什么 | 日志匹配稳定后流水线发送,多批在途并乐观推进 Next,提高高 RTT 链路吞吐。 | Progress三态 |
| Progress Snapshot 状态做什么 | Follower 所需日志已 Compact 时暂停普通 Append,发送并等待安装 Snapshot;完成或失败后恢复探测。 | Snapshot状态 |
| Inflights 有什么作用 | 限制流水线在途 Append 消息和字节,防止慢 Follower 吞掉内存和网络;过小又会限制吞吐。 | Inflights |
| RejectHint 为什么比 nextIndex-- 快 | Follower 返回冲突 Index/Term,Leader 可跳过整段相同 Term,而不是百万条日志逐条往返回退。 | 按Term回退 |
| 乱序 Reject 会不会把 Next 回退错 | 实现要先判断 Reject 是否已过期;若 rejectedIndex 已被 Match 确认,就忽略旧 Reject,维持 Match < Next。 | Progress不变量 |
| AppendResp 为什么必须先持久化日志 | Leader 把 ACK 计入 Quorum;若 Follower 只在内存接收后 ACK、断电丢失,会让“多数持久化”假设不成立。 | 持久化顺序 |
| Leader 何时推进 CommitIndex | 当前配置 Quorum 的 Match 达到候选 Index,且该位置 Entry Term 等于 Leader 当前 Term,才能按多数计数直接推进。 | 提交规则 |
| 为什么不能直接提交旧 Term 多数条目 | 经典 Figure 8 窗口中,旧条目短暂分布到多数仍可能被更高 Term 候选覆盖;当前 Term Entry 提交后才锁定前缀。 | 当前Term限制 |
| 旧 Term 日志怎样最终提交 | 当前 Leader 提交一个当前 Term Entry 后,它之前的旧 Term 前缀一并确定,无需逐条改写 Term。 | 提交规则 |
| Append、Persist、Commit、Apply 区别 | Append 是本地日志出现,Persist 是稳定存储,Commit 是协议确定不可覆盖,Apply 是状态机执行;客户端响应还可能丢失。 | 五阶段 |
| CommitIndex 和 AppliedIndex 差距说明什么 | commit-applied 大表示共识已决定但状态机执行慢,应查 FSM/Backend,而不是只调网络复制。 | Apply Lag |
| Ready 是什么 | etcd/raft 输出给应用的工作批次,包含 HardState、Entries、Snapshot、Messages、CommittedEntries 和 ReadStates。 | Ready契约 |
| Advance 表示什么 | 应用确认上一批 Ready 的保存/Apply 进度,允许 Raft 安全输出后续工作;异步存储模式有不同完成消息契约。 | 事件循环 |
| MustSync 什么时候为 true | etcd/raft 在有新 Entries 或 Term/Vote 变化时要求同步;仅 Commit 前进通常不需要重新 fsync 已稳定日志。 | Ready契约 |
| Leader 为什么不等所有节点 ACK | Quorum 已足以保证交集和故障容忍;等待最慢节点会把集群延迟绑定到最慢副本。 | 性能模型 |
| 慢 Follower 可以完全不管吗 | 不能。短期不阻塞最快 Quorum,但长期会增加 WAL、Snapshot、磁盘和恢复风险,且下一个故障可能让它进入关键多数。 | 性能模型 |
四、线性读、Snapshot 与成员变更
| 问题 | 标准回答 | 原理 |
|---|---|---|
| Leader 本地读为什么可能旧 | 旧 Leader 失去多数但尚未察觉时,新多数可能已选 Leader 并提交新值;只看本地角色不够。 | 读语义 |
| ReadIndex Safe 怎么工作 | Leader 用唯一 Context 通过 Heartbeat 获当前配置 Quorum 确认,返回安全 Index;本地等待 AppliedIndex 达到它后再读。 | ReadIndex |
| ReadIndex 返回为什么不能立刻读 | Quorum 只确定安全读屏障,本地状态机可能还没 Apply 到该 Index,直接读仍会旧。 | ReadIndex |
| 新 Leader 为什么暂缓 ReadIndex | 当前 Term 尚无已提交 Entry 时领导权和日志历史未建立安全联系;空 Entry 提交后再释放 Pending Read。 | ReadIndex首条提交 |
| LeaseBased Read 前提是什么 | 必须有 CheckQuorum,并对时钟和进程暂停有可信上界;时钟倒退或长暂停会削弱租约假设。 | Lease读 |
| Follower Read 一定错误吗 | 不一定,若 API 明确允许 Stale/Serializable Read 可用于低延迟场景;不能把它宣称为线性一致。 | 读语义 |
| Snapshot 必须包含什么 | 状态机数据、LastIncludedIndex、LastIncludedTerm、ConfState,以及实现所需版本和校验元数据。 | Snapshot模型 |
| Snapshot 为什么只能基于 AppliedIndex | Commit 尚未 Apply 时状态机数据并不包含该命令;把 Metadata 写到更高 Index 会让恢复跳过数据。 | Snapshot模型 |
| Compact 为什么不能超过 Applied | 被删日志必须已有可恢复 Snapshot 覆盖;否则节点重启或 Follower 追赶时无法重建状态。 | Compaction |
| Snapshot 安装为什么要原子 | 状态机数据和 Raft Metadata 若一边成功、一边失败,会重复 Apply 或跳过命令。 | Snapshot安装 |
| ReportSnapshot 为什么重要 | Leader 在 Snapshot 状态暂停普通 Append;传输/安装失败若不报告,Follower 可能永久卡住。 | Snapshot安装 |
| Joint Consensus 是什么 | 过渡配置同时要求旧配置多数和新配置多数,保证任意合法决策集合仍相交,再提交 LeaveJoint 切到新配置。 | Joint Consensus |
| 为什么不能直接 A/B/C 换 C/D/E | 旧多数 A/B 与新多数 D/E 不相交,两个分区可能分别提交冲突历史。 | Joint Consensus |
| 为什么一次只能一个未Apply配置变更 | Quorum 计算必须与日志中的成员历史一致;pendingConfIndex 防止成员视图并发交错。 | 配置串行 |
| Learner 是什么 | 接收日志但不计入投票 Quorum 的节点,用于安全追平和成员替换;它不提高故障容忍。 | Learner |
| 为什么 Learner 追平后才能 Promote | 空节点直接成为 Voter 会提高 Quorum,旧节点再故障就可能立即失去可用性。 | Learner Promote |
| 3节点加第4个Voter更可靠吗 | 多数从2变3,仍只容忍1故障,还增加复制成本;通常先加 Learner,再形成5 Voter 才提升到容忍2故障。 | Learner与Quorum |
| Snapshot 和 WAL 恢复顺序是什么 | 加载最新完整 Snapshot,恢复 ConfState/FSM,再校验重放其后 WAL,恢复 HardState/Commit,Apply 尚未 Apply 的已提交条目。 | 崩溃恢复 |
| 多数永久丢失还能自动恢复吗 | 不能证明少数历史包含所有 Commit;必须人工选择权威 Snapshot/WAL,明确 RPO,以新集群身份恢复并隔离旧集群。 | 灾难恢复 |
五、Paxos、ZAB 与产品边界
| 问题 | 标准回答 | 原理 |
|---|---|---|
| Basic Paxos 三个角色是什么 | Proposer 提案,Acceptor Promise/Accept,Learner 学习多数选定值;一个物理节点可承担多个角色。 | Paxos角色 |
| Paxos Phase 1 做什么 | Proposer 发 Prepare(n),多数 Acceptor 承诺不接受更小编号,并返回自己最高已接受提案。 | Prepare/Promise |
| Paxos Phase 2 做什么 | Proposer 必须继承响应中最高编号已接受值,再发 Accept(n,value);多数接受后该槽位被选定。 | Accept |
| Multi-Paxos 为什么需要稳定 Leader | Basic Paxos 每槽位完整 Prepare 成本高;稳定 Leader 建立领导权后可主要执行接受阶段,降低往返和冲突。 | Multi-Paxos |
| Raft 是 Paxos 换名字吗 | 不是。两者都可实现共识且常有稳定 Leader,但状态拆分、消息、日志安全证明和成员变更表达不同。 | 算法对比 |
| ZAB 为 ZooKeeper 解决什么 | 提供 Leader 恢复、历史同步和 Proposal/ACK/COMMIT 原子广播,保证事务按全局顺序交付。 | ZAB |
| zxid 和 Raft Index 一样吗 | 都可表达事务顺序,但 zxid 通常含 Epoch 与计数,协议和恢复语义属于 ZAB,不能逐字段等同 Raft Index/Term。 | ZAB恢复 |
| DIFF、TRUNC、SNAP 是什么 | ZAB 同步中分别用于增量补齐、截断冲突尾部和传输完整快照,具体实现随 ZooKeeper 版本变化。 | ZAB阶段 |
| etcd Revision 等于 Raft Index 吗 | 不等于。Raft Index 是共识日志位置,Revision 是 MVCC 键空间逻辑版本;二者属于不同抽象。 | etcd Raft与MVCC |
| Consul Gossip 是 Raft 吗 | 不是。Server 用 Raft 管理强一致控制状态,Agent/Serf Gossip 用于成员和健康传播,职责不同。 | 产品边界 |
| Kafka KRaft 管理普通消息副本吗 | KRaft 主要管理集群元数据 Quorum;Topic Partition 数据复制协议不能简单说成“全部由元数据 Raft 复制”。 | 产品边界 |
| Redlock 是 Raft 吗 | 不是。Redlock 是多个独立 Redis 的多数加锁尝试,不是复制状态机共识;关键外部写仍需要 Fencing。 | 组件边界 |
六、生产场景题
6.1 集群显示有 Leader,但写请求一直超时
我会按 Append、Persist、Commit、Apply、Respond 五阶段拆分:先确认 Leader 是否仍能收到当前配置 Quorum 的 AppendResp,再查每个 Follower Match/Next/Progress、WAL fsync、Pending Proposal 和大 Entry;若 Commit 已前进但 Applied 落后,问题在 FSM/Backend。客户端用相同 requestId 查询事实,不直接重复业务写。
原理:五阶段 · 写超时Runbook
6.2 Leader 频繁切换,应该直接把 ElectionTimeout 调大吗
不能先调参数。要把 Leader Change/Term 与网络 RTT/丢包、双向可达、GC/进程暂停、CPU Steal、WAL fsync P99/Max、证书和 PreVote/CheckQuorum 时间线关联;根因修复后再按正常尾延迟调整超时。盲目调大会延长真实故障恢复。
6.3 隔离节点重连后为什么把稳定 Leader 弄下台
未启用 PreVote 时,隔离节点可能反复增加 Term;重连携带更高 Term 会让旧 Leader 退位,即使它日志太旧也选不上。PreVote 先询问未来 Term 的获票可能,不直接涨 Term,可减少这类扰动。
原理:PreVote
6.4 旧 Leader 上有一条新日志,网络恢复后为什么消失
它只在少数派本地 Append,没有 Quorum Commit,因此允许被新 Leader 的合法历史覆盖。Raft 保证已提交日志不丢,不保证所有曾经 Append 的日志保留。
6.5 ReadIndex 已返回,读仍然得到旧值
先查本地 AppliedIndex 是否达到 ReadIndex。Quorum 确认只给出安全读屏障,状态机 Apply 可能落后;还要确认读的是同一 Raft Group/FSM,而不是旁路缓存或另一集群。
原理:ReadIndex
6.6 单个 Follower 一直卡在 Snapshot 状态
查看 PendingSnapshot、文件创建/校验/传输/安装、磁盘容量和临时目录;重点确认应用是否在失败时调用 ReportSnapshot。Leader 在 Snapshot 状态暂停普通 Append,吞掉失败会永久卡住。
原理:Snapshot安装
6.7 扩容后集群反而不能写
检查是否把空节点直接加为 Voter,导致 Quorum 从2升到3,而新节点尚未追平或不可达。正确做法是加 Learner,观察 Match/Applied/Snapshot,追平后 Promote,再逐个移除旧成员。
原理:Learner
6.8 CommitIndex 正常前进但请求越来越慢
比较 CommitIndex 与 AppliedIndex;若差距扩大,Raft 已完成决策,瓶颈在状态机 Apply、Backend、锁、Batch 或 GC。需要入口反压并优化确定性 Apply,不能只增大 Inflights。
6.9 磁盘监控平均延迟不高,为什么仍频繁选举
Raft 对尾延迟敏感,应看 WAL fsync P99/Max、队列深度和同时发生的 GC/CPU 抢占。少量超长 fsync 足以错过多个心跳,平均值会掩盖故障。
原理:性能模型
6.10 客户端超时后重试造成配置版本跳两次
说明业务只依赖网络响应,没有稳定 requestId 去重。第一次可能已 Commit/Apply 但响应丢失;重试应携带同一 ID,由状态机返回原结果或 CAS 同一业务版本。
原理:客户端幂等
6.11 Snapshot 恢复后状态机少了一段数据
核对 Snapshot 数据与 LastIncludedIndex/Term/ConfState 是否来自同一一致点,是否把未 Apply 的 Commit 写进 Metadata,WAL 是否从正确 Index 重放,以及 FSM 数据与 AppliedIndex 是否原子保存。
原理:Snapshot模型 · 存储顺序
6.12 多数节点永久丢失,能否选剩余最新节点直接启动
不能称为正常 Raft 恢复,因为无法证明少数历史包含所有已提交条目。应冻结旧集群,比较 Snapshot/WAL/Term/Index/校验和,明确 RPO,以官方 Restore/Force New Cluster 建新身份,并防旧节点回归。
原理:灾难恢复
6.13 一个日志 Entry 放了100MB文件会怎样
Quorum 必须复制、持久化和恢复整个 Entry,会造成 WAL、网络、GC、Inflight 和 Snapshot 尖峰。大对象应放对象存储,日志只提交不可变引用、摘要和业务元数据。
6.14 新旧配置切换时发生分区,怎样防两个多数
使用 Joint Consensus:过渡期每次 Commit 同时满足旧配置和新配置多数,EnterJoint 提交后再 LeaveJoint;不能直接把 A/B/C 替换成 C/D/E。
6.15 多个 Raft Group 就能自动完成跨分片事务吗
不能。每个 Group 只对自己的日志达成共识;跨 Group 原子性仍需事务记录、时间戳、2PC/并发控制等更高层协议。Multi-Raft 解决分片并行复制,不消灭跨分片协调。
原理:Multi-Raft
七、项目回答模板
我们把 Raft 定位为控制面复制状态机,不承载大业务文件。节点通过 PreVote/RequestVote 选主,候选先比 LastLogTerm 再比 Index;Leader 上任追加当前 Term 空条目。每次提议先由 Ready 输出 Entries/HardState,WAL 持久化后才发送 Append,Follower ACK 后 Leader 按 Progress.Match 计算 Quorum,并只直接提交当前 Term Entry;状态机 Apply 后再返回业务结果。线性读使用 ReadIndex,获得 Quorum Context 后等待 AppliedIndex。成员替换先加 Learner 追平再 Promote,复杂变更走 Joint Consensus。生产按 Term/Leader Change、Match/Next、Commit-Applied Lag、WAL fsync、Snapshot 和 requestId 去重排查,客户端超时永远按结果未知处理。
本章小结
共识面试不能只回答“Leader 复制到多数”。高质量回答必须讲清持久化先于响应、PreVote/CheckQuorum、Index+Term 日志匹配、Progress 三态、当前 Term 提交限制、Commit 与 Apply、ReadIndex、Snapshot 原子恢复、Joint Consensus、Learner 和客户端幂等,再用可执行 Runbook 落到生产。
