从消息到分布式共识
客户端超时,也可能发生在写入成功之后。三节点 Raft 服务中,领导者获得两份持久副本的证据,就能提交并应用当前任期命令,即使第三台仍落后。之后响应路径失败,客户端便无法确认成功。字节送达、持久化、提交、应用、收到结果是不同事件。
一张收据只证明它对应的阶段:送达不等于存档,存档不等于按协议获准执行,获准后通知也可能没送到。不能把每种确认都解释为整个请求完成。
副本落盘与领导者获知分开
客户端 C 发送请求 m42,要求 set x=7。S1 已是三节点集群的领导者,S2、S3 为跟随者。三台初始日志都到索引 2,已应用该前缀,x=0。
请求使用已建立的 TCP。TCP ACK 确认字节接收,不证明应用已解析或写盘。在该停顿点,全部持久日志仍到 2。TCP 是应用协议之下的可靠字节流,见 RFC 9293。
S1 接受命令,在当前任期 7 的索引 3 保存 3/t7;3 是位置,7 是选举任期,不是服务器编号或秒数。AppendEntries 把该项及匹配前缀 2/t6 发给 S2,S2 持久化后才应答。
此时两个磁盘已有副本,S1 却只知道自己这一份。回复到达后才能计入 S2。对于当前任期条目,已知两份持久副本达到多数,S1 可把 commitIndex 推到 3,再按日志顺序应用,将 lastApplied 推到 3,x 变为 7。持久日志、提交、应用各有状态,见 Raft 论文图 2 与 §5.3。
本服务在领导者应用后响应成功。S2 即使已有 3/t7,commitIndex 仍可暂为 2,需后续心跳带来新提交边界,才在本地提交、应用。响应不必等待所有跟随者应用,参看 Raft Apply 生命周期。
消息到共识逐步演示
默认响应丢失,值为 7,S3 全程延迟。
- C 发送 m42,S1 的 TCP 栈接收字节并回 ACK。客户端只获得传输收据,所有持久日志仍到 2。
- S1 解析命令并保存
3/t7,已知副本1/3。commitIndex=2、lastApplied=2、x=0 不变。 - AppendEntries 携带
3/t7、前缀2/t6和旧提交边界 2,S2 保存。两份已落盘,但 S1 尚只知道一份。 - S2 的 AppendEntries OK 到达,S1 知道
{S1, S2}已持久化,达到两台门槛。条目任期等于当前领导者任期,S3 无需参与。 - S1 先把 commitIndex 设为 3,应用状态仍 x=0;下一节拍才把 lastApplied 设为 3 并写 x=7。
- S1 生成 m42 成功结果,但客户端连接在交付前不可用,并持续到超时期限。C 超时,结果未知;S1 保留提交及 x=7。
- 与客户端期限独立地,S1 发
leaderCommit=3心跳,S2 先更新 commitIndex,再更新 lastApplied 和 x=7。S3 仍为旧前缀和 x=0。 - 最终并列显示客户端的不确定,与服务已提交、已应用的事实。客户端缺少能确认成功的应用响应。
七项结果链记录阶段产物:存储条目用于复制,多数证据允许提交,已提交条目可供应用,最后一项必须有结果实际到达客户端。末次交接失败不会抹掉之前已完成的步骤。
超时为何意味着结果未知
保持值 7,只改场景:
| 场景 | 本轮最终可观察服务状态 | 客户端证据 |
|---|---|---|
| 正常响应 | S1、S2 存储并应用索引 3,S3 落后 | 收到成功,x=7 |
| 响应丢失 | 服务状态历史与正常响应相同 | 收到 TCP ACK 后超时,应用结果未知 |
| 无多数 | 只有 S1 存索引 3,应用都停在 2、x=0 | 收到 TCP ACK 后超时,应用结果未知 |
无多数场景中,S2 的复制路径也不可用,S1 无法取得第二份持久副本,最终只有未提交条目。观察者能看见这个状态,客户端只知道没等到响应;而响应丢失场景同样超时,命令却已执行。
超时属于调用者的等待规则,不是服务端回滚指令。未提交条目日后可能提交,也可能在恢复中被替换,本课不预设它的未来。
重试需要应用自己的契约。这里 set x=7 是赋值,若改成 increment x,第一次执行后丢响应,再提交一个新日志项可能执行两次。关联 ID 本身不等于去重。Raft 排序日志,服务要抑制重复请求还需请求身份与结果跟踪,见 Raft 论文 §8。调用方应保留“结果未知”,按 API 的状态查询或幂等重试契约处理,共识不会自动把新提交等同于旧操作。
边界情况
- 输入 0 时,应用 set x=0 不改变可见值,但 lastApplied 仍到 3,命令确实处理过。
- S3 在全部场景停于 2。多数进展不要求同时应用;落后的日志前缀与同索引应用冲突命令不同。
- S2 回执在途时,两个日志已有
3/t7,S1 证据仍是一份,不能使用尚未收到的信息。 - 直接多数提交只适用于当前领导者任期创建的条目。旧任期条目还有额外规则,不能删掉该条件照搬。
- 响应场景让连接持续不可用直至期限,不是把一次 TCP 分段丢包当永久丢失;普通丢包通常会重传。
- 输入为 −99 到 99 的整数,是标签显示范围,不是 Raft 限制。
常见错误
- 混用确认含义:TCP ACK 证字节接收,AppendEntries OK 证跟随者持久保存匹配项,应用响应才向客户端确认服务结果。
- 把客户端算成副本。多数只在 S1、S2、S3 中计算,C 的传输确认没有日志副本。
- 本地追加一次就报告成功。无多数场景正是已落盘但未提交。
- 有日志就立即应用。S2 必须等获知提交边界。
- 所有服务器同时变完成。每台独立获知并应用,后续通知含第一次复制 RPC 尚未带来的提交信息。
- 把超时解释为未执行。响应丢失时 S1 的 lastApplied=3,没收到回复不会撤销执行。
模型范围
本课固定三个非拜占庭服务器、已选举领导者、相同初始日志和一次当前任期写入。连接建立和 TCP 恢复合并展示;省略选举、常规心跳、持久化延迟、批量、乱序、换主、旧任期提交、读、客户端重试去重、快照、成员变化。消息逐个安排,回放秒数不是网络测量。响应策略等待领导者应用,其他 API 可定义不同完成条件。继续看 Raft 共识 的选举与日志修复,或 TCP 握手 的连接机制。