Skip to content

复制服务综合任务:从成功响应到恢复与交接 ​

本任务从既有Raft和线性一致性出发,目标是把它们变成对客户端负责的完整服务。先读状态机复制新增的成功历史映射,再走会话、读屏障和恢复;配置与外部副作用作为第二条支线。固定配置共识的Paxos/Raft证明仍留在原页,不需要重新抄一遍。

你可以先在纸上作答,再下载标准库事件检查器,执行python foundations-service-checker.py。它运行3415项断言,包含243个五请求词及每个前缀的快照重放,打印从事件实际计算出的状态和反例。脚本可导入并改变事件输入;不是完整Raft实现、磁盘模拟器或任意长度协议证明。检查器的三副本日志从索引1起,本页余额主例从已知检查点10起,两个编号不能混抄。

开始前:四问定位入口 ​

先不看核对标准,口头或纸上回答:

  1. ABC三个固定投票者至少要几份有效确认才能形成多数?A单机已经fsync,是否够向客户承诺Raft提交?
  2. 某副本commitIndex=12,lastApplied=10。它已经知道什么,又还没完成什么?能否直接把状态10当成状态12读出?
  3. set(x,1)已成功返回之后,另一客户才调用read(x),中间没有别的写。读0能否靠调整线性化点变合法?
  4. 服务端已执行一次加10,但成功响应在网络里丢了。客户端超时能否宣布“没有加钱”,并换新业务身份重试?

核对标准:第1问是2份,单机持久化不等于多数提交,还需当前任期等Raft规则;第2问知道提交前缀到12,但业务只执行到10,必须补应用证据;第3问不能,响应先于新调用的实时边必须保留;第4问不能,结果仍未知,换身份可能重复效果。

若第1问答不稳,先读quorum和Raft提交规则;第2问回日志的追加、提交、应用;第3问回线性一致性;第4问先完成RPC结果判定。这些是本单元会实际使用的入口,不要求先通读FLP、拜占庭协议或完整数据库恢复算法。

1. 为每个成功结果找位置 ​

三副本A、B、C的稳定前缀都到10,业务余额100。A是任期5的leader;会话s已注册,最近序号0,客户端每次得到结果后才发下一个逻辑请求。

按下面事件填写每步的“已存日志、commitIndex、lastApplied、余额、s的最近序号及结果、客户端已知结果”:

  1. P调用(s,1,add(10));A本地追加索引11
  2. B稳定接收11并确认,A按本任期多数规则提交11;C仍到10
  3. A应用11,产生110,响应丢失;P到期,只能报告结果未知
  4. P保持相同s、序号和命令重试,A把它追加为12;A、B多数提交,A应用12并返回110
  5. P收到110后发(s,2,add(5)),索引13提交并应用,返回115
  6. 更早的一份序号1在途尝试进入14,提交并应用

核对答案:第2步可以有commit11但仍applied10、余额100;第3步是余额110、h=1、结果110。第4步余额仍110,只有applied升为12。第5步变115、h=2、结果115。第6步applied14,余额115,旧序号返回RESULT_FORGOTTEN,不能把115冒充序号1原结果,也不能重新加10。

逻辑请求1的调用区间从第一次发起延续到重试取得原结果;可把它放在11首次提交的时刻,位于这个区间内。日志12不是第二次业务操作。请求2在请求1成功后才调用,所以顺序见证为“加10得110;加5得115”。如果把请求1的成功提前到第1步本地追加之后,随后隔离A、由B/C覆盖11,哪个已经完成的操作失去合法位置?请明确区分本地持久化与分布式提交。

2. 修复两种读错误 ​

另开寄存器例子,初值x=0,三节点应用到10。A任期4时被隔离,B/C选出任期5的B。B在11提交并应用set(x,1),写客户端收到成功后,另一客户端才向A调用读。

  • A直接返回0为什么没有合法线性化?写响应在读调用之前,不能把读排到写前
  • A用昨天的多数心跳证明自己还是leader,缺什么?缺少与这次新读关联的有效多数确认
  • B已提交11但只应用到10,新轮确认成功后立即读0,缺什么?缺lastApplied>=readIndex的应用屏障
  • B刚当选、尚未提交本任期条目,为什么不能只取自己记得的旧commitIndex?先提交本任期no-op,才能把已继承的前任前缀接到已知提交下界

按ReadIndex写出一条修复轨迹:先具备当前任期提交锚,取r=11或更大,再发本读新轮探测,取得合法多数,等待应用到r,最后在一致状态版本上读取。若与set(x,2)重叠,读1和读2分别可以放在什么位置?若读取一半状态11、一半状态12,即使每个字段单独合理,也不是本页的一次前缀查询。

3. 给恢复包一个完整截点 ​

回到余额例,取前缀12,正确恢复包包括(k=12,term=5,balance=110,session s:h1/result110,configuration),以及影响后续转移的身份分配元数据。索引13为add(5)。

先比较三种接收者:

  • C应用到10,日志12的term也是5:安装12并保留13,但必须等13的提交证据才能执行,最终115
  • C应用到10,日志12的term是4:旧13接在冲突前缀后,应丢弃尾部再由leader重传
  • C已应用到14:收到12的旧快照按本页保守策略忽略,不回退状态

再在“临时写一半、全文件稳定但根未发布、根稳定但旧日志只删一部分”三个位置掉电。正确顺序分别可恢复旧代、旧代、新代,始终至少有一份完整证据。反过来先删11/12再保存快照,会留下无法由旧快照10恢复的空洞。

最后检验结果表损坏:整份s会话丢失时,严格协议拒绝未知会话,原响应不能取回;错误把s恢复成活跃h=0时,重试会让110变120。只有余额和结果表碰巧挡住一次重放,不足以证明应用位置正确。要求状态、结果、位置和配置来自同一前缀,而不是只看最后余额。

4. 把ABC交给CDE ​

设O=ABC、N=CDE。先枚举所有二人或三人响应集合,分别计算旧多数、新多数、并集多数。必须能够解释:AB、DE各自可满足一边但不满足joint;ACD满足joint;ABD虽是并集3/5,却不满足新侧多数;ABDE不含公共节点C也能满足joint。

按两条配置项执行:21为J,存于A/C/D/E并以A/C/D双多数提交;22为N,存于A/C/D并以C/D新多数提交,A随后退位。此时B仍O,E仍J,A/C/D是N。三种本地配置可以同时存在,安全承诺禁止的是它们绕过已提交过渡证据形成冲突决定,不是要求每个节点同步更新清单。

迁移题:若把N改成DEF,完全没有成员交集,J阶段是否仍可定义?可以,必须同时获得ABC的至少2票与DEF的至少2票;至少4个响应,而非六节点并集4/6即可任选。写出一个四人并集多数却不满足joint的集合,例如ABCD,只含新侧D。再说明学习者追赶为什么影响交接可用性,却不能取代日志和quorum证据。

5. 阻挡旧人,识别重复 ​

资源R水位40,P拿41后暂停,Q拿42。列出两个不同终点:

  • Q尚未把42送到R,P的41到达:最小fencing模型可接受;协调服务授权不等于资源已完成交接
  • Q在R激活42并确认、写9之后,P用41写8:必须拒绝,值仍9

若检查代际和写入不是一个原子步骤,构造P先检查41、Q写42/9、P随后写8的三步竞态。再让42号同一扣款发送两次,说明fencing为什么还需请求去重。资源重启后忘记水位、集群重建复用编号、分片绕过检查,各自破坏哪项前提?

6. 订单成功不等于仓库成功 ​

源事务原子提交order7=CONFIRMED和outbox m7;仓库初始库存5。中继发送m7,仓库提交预留使库存4,确认丢失;中继崩溃后重发。

没有接收去重时库存可到3,有原子Seen[m7]+预留结果时保持4。两者都发送和接收了两次,差别在业务效果。中继不能先标记完成再发送,否则发送前崩溃会丢通知;标记放后则必须接受重复可能。

把API承诺分成三栏:源订单与意图已提交;仓库效果已提交;源端已保存仓库确认。第一个成功响应能证明哪一栏?若仓库还要调用一个无去重短信接口,库存一次效果能否证明短信也一次?答案都须按outbox的原子域说明,不能统一写“exactly-once”。

自测标准与费用账本 ​

完成不是背出六个名词,而是能对每份成功或错误响应说明其证据、所属尝试/逻辑请求、覆盖的原子域,以及恢复后证据还在哪里。至少独立修改一个事件,取得一个失败历史,再明确哪条规则修复它。

  • 三副本稳定写按2/3提交,消息、日志payload、持久化和应用分别计费
  • 一批ReadIndex增加多数往返与应用等待;无每读日志不等于无通信
  • 快照B字节加m项后缀需约B+mc字节,传输和重放都有成本
  • 活跃S个串行会话保留O(S)份最近结果;并发w个需相应结果集合和回收水位
  • joint须同时满足两边多数,可用性取决于两组,不只看并集人数
  • outbox积压与接收去重保留会占空间,有限保留期必须与允许重试期相容

所有检查都基于诚实节点、声明的持久接口和确定性业务。不会仅靠有限轨迹证明所有异步执行安全;也不会因状态机安全就保证永久分区时进展、时钟租约正确或外部世界可回滚。