先记住这个答案
在Raft中,一个日志条目被标记为committed需同时满足:已被持久化到集群中超过一半的节点(包括Leader),且当前任期Leader已将commitIndex推进到该条目。此时Leader认为该条目已被安全提交,并通过心跳或AppendEntries把提交索引广播给所有节点。注意:只有当前任期Leader才能推进commitIndex,因此旧条目即使曾被复制到多数节点,若未得到当前任期Leader的间接确认,也不能直接视为committed。
- 复制到多数派且Leader推进提交索引后才算提交
- 只有Leader能推进提交索引
- 提交后可安全应用到状态机
提交索引的推进机制
Leader接收到客户端写入请求后,先将条目追加到自己的日志,然后并行向所有Follower发送AppendEntries RPC。当Leader收到多数节点的成功响应时,它认为该条目已在多数节点持久化,于是将本地的commitIndex推进到该条目,并通过下一次心跳或AppendEntries把新版commitIndex广播给所有节点。收到广播的Follower会按顺序将已提交条目应用到状态机。
提交条件与任期相关。Raft要求只有当前任期的Leader才能推进commitIndex,且旧条目不能仅凭多数派拥有就被直接提交。旧Leader可能尚未完成提交就崩溃,而新Leader缺少这些条目,若立即提交会导致状态机不一致。因此新Leader会先提交自己任期的条目,再顺带确认旧条目的安全性。
五节点集群的提交过程
以5节点集群A、B、C、D、E为例,A是Leader。客户端写入x=1,A将日志条目追加到索引5。A向B、C、D、E发送AppendEntries,收到B和C的确认,加上自己共3个节点,达到多数派。A立即将本地commitIndex更新为5,应用x=1到状态机并返回客户端成功。随后A通过心跳广播commitIndex=5给所有Follower,它们也会顺序应用。
如果只有B成功而C、D、E超时,则仅有A和B拥有该条目,未过半,A不能提交。此时即使客户端持续等待,Leader也不能返回成功。若A在超时前崩溃,新选举出的Leader可能不包含索引5的条目,该条目会被丢弃。多数派条件保证了已提交记录不会在后续选举中丢失,这是Raft可靠性的核心。
无法提交的场景及其代价
网络分区会破坏多数派。假设A被隔离在少数派侧(只有A和B),它无法联系C、D、E,因此无法提交任何新日志。多数派侧则通过选举产生新Leader并继续处理写入。此时少数派Leader若继续接受请求,请求将永远无法提交,客户端只能超时重试或迁移到多数派侧;系统应当拒绝写以避免脑裂。
另一个边界是旧Leader未提交的条目。新Leader必须持有所有已提交条目,但可能缺少某些只存在于旧Leader上的未提交日志。新Leader不会自动提交这些日志,即使它们曾被复制到少数节点。只有当新任期产生至少一条新提交后,通过日志索引关系间接确认旧条目安全,才能提交。这避免了提交已被覆盖的陈旧记录。
容易答错的地方
- 复制到一半就算提交
- 错误。如果复制到的节点数没有超过一半(例如只有两个节点中的Leader和一名Follower),则未达到多数派,条目不能提交。此时Leader崩溃可能丢失该条目。
- Follower也能主动提交日志
- 不。只有Leader知道并推进commitIndex,Follower不能因为自己收到了日志就标记为已提交。Follower必须根据Leader广播的commitIndex来应用日志,否则可能提交被后续覆盖的条目。
面试官还会怎么问?
如果Leader在提交前崩溃,客户端重试会发生什么?
旧Leader崩溃后,新Leader可能不含该条目,客户端需要重新发送写入请求。只有被多数派复制且由新任期确认的条目才能保证持久。
如何判断一个日志条目已经被提交?
Raft没有对外暴露提交查询,但客户端在收到Leader写响应后即可视为已提交。内部可通过查看Leader的commitIndex来确认。
网络分区时,少数派一侧能继续写吗?
不能。少数派无法凑成多数派,Leader无法提交日志,写请求永远无法获得成功响应。系统设计上优先保证多数派可用,牺牲少数派的可用性。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。