Skip to content

应用Application

状态机复制

State machine replication · SMR

让多个副本按同一确定顺序执行命令,从而实现容错服务。

形式陈述 ​

将服务建模为确定性状态机

δ:S×Cmd→S×Out,

并固定初始状态 s0。状态机复制要求所有正确副本从相同初态出发,以相同顺序取得命令 c1,c2,…,并逐项应用 δ。于是对每个索引 i,应用到该位置的副本具有相同状态 si 和输出 oi。复制日志提供 committed 命令序列的接口;状态机复制的语义中心是确定性重放怎样把共同前缀变成共同状态,而不是再次定义日志的本地追加、提交和应用规则。

完整服务语义还要固定客户请求标识、重试去重、命令何时视为提交、何时可对客户响应,以及快照恢复和成员重配置如何与日志连接。若要对外提供线性一致的读,还需额外的读取协议;“日志相同”本身不是完整的端到端规格。

直觉

与其不断同步整个状态,状态机复制让排序层先提供共同命令前缀,再由各副本在本地重放同一计算。它由此把“多台副本如何保持一致”分成两层:共识或原子广播负责决定顺序,执行层让确定性状态机从相同初态按该顺序运行。仅仅复制数据或周期同步快照不够;若命令顺序不同,即使每条命令本身合法,也可能得到不同状态。反过来,同一日志也无法消除执行过程中未被记录为输入的非确定性。

状态机复制:共同日志确定共同状态
例子与边界

初始余额为 10,命令 withdraw(12) 与 deposit(5) 若按不同顺序并带余额检查,会产生不同结果:先取款则取款失败、最终余额为 15;先存款则取款成功、最终余额为 3。共识日志固定顺序后,各副本便一致执行。客户端重试若没有请求 ID 去重,同一命令可能因网络超时被提交两次,日志一致仍会得到错误的端到端语义。

执行还必须保持确定性:读取系统时间、随机数或外部服务会引入非确定性,必须转化为已排序输入或通过协议协调。“多数副本存有某条日志”也不自动意味着客户端已看到线性一致结果;还需领导者提交规则、读取协议和会话处理。成员重配置必须纳入日志,并通过相交配置让安全证据跨成员视图延续,避免新旧配置各自决定冲突命令。

推论与应用

在最多 f 个副本可能任意作恶、认证通信且副本数满足典型的 n≥3f+1 条件时,实用拜占庭容错通过 pre-prepare、prepare 与 commit 法定人数实现确定状态机的共同命令序列。它面向拜占庭故障和部分同步进展条件,不能把其副本数、消息成本或视图切换保证直接当作崩溃故障 Raft/Paxos 的同义实现。

状态机提供确定性转移语义,复制日志提供共同命令前缀;Paxos、Raft与Viewstamped Replication是日志层的具体协议。状态机复制由此支撑高可用键值存储、配置服务、数据库元数据和区块链执行模型。

形式化验证可以把实现状态映射到“共同 committed 前缀 + 确定状态”的抽象机,并用精化关系说明选主、重试和快照等内部步骤不改变外部历史。模型检查适合在有限副本和有界状态下搜索不变式反例,协议特有的 ballot、term 或选举机制仍留在各协议页,本页只规定排序层到确定性执行层的接口。

对外实现线性一致性还依赖正确的请求应答边界、读路径与去重;快照和日志压缩则在不改变已执行命令历史含义的前提下降低恢复成本。

从三份日志到一次成功响应 ​

固定A、B、C三个非拜占庭副本,沿用Raft的稳定任期、日志与多数提交规则。业务是初值100的整数余额,add(d)返回更新后的余额;本段先把每个请求当成唯一一次逻辑操作。客户端调用、leader接收、日志追加、多数持久确认、提交、应用、响应是不同事件,只有最后一项出现在客户端的完成历史里。

  1. 时刻1,客户P调用add(10);A在当前任期5把它追加为索引11
  2. 时刻2,A与B的稳定日志含11,C仍到10;时刻3,A按当前任期多数规则提交11
  3. 时刻4,A按顺序应用11,余额110,保存输出110;时刻5,P收到成功响应110
  4. 时刻6,客户Q才调用add(5);它被放到索引12,时刻7获得A、B多数并提交,时刻8应用得到115,时刻9返回115

成功历史的顺序见证是add(10)->110在add(5)->115之前。可把本例写操作的线性化点选在各自首次成为不可撤销已提交项的时刻3与7:都位于调用和响应之间,确定性应用给出相应返回值。副本C稍后才应用11、12不影响这个客户端历史,它在追赶前不能未经读协议就返回旧余额。

一般构造按首次有效业务命令的日志顺序排列操作。每次只应用已提交前缀,且响应在应用产生结果之后;若X已返回、Y才调用,则Y不可能占据X之前的已提交槽位。新leader继承已提交前缀,因而跨换主仍保持这个实时顺序。这个论证对读还不完整:日志读按其槽位取结果,绕过日志的读则需读屏障与ReadIndex补齐权限与应用证据。不可把任意leader本地读强行塞进上述写顺序。

失败轨迹、未知与逻辑请求边界 ​

把第一段时刻5提前到“A只在本地稳定追加11”之后,A随后隔离,B、C可以合法选出新leader并覆盖这个未提交尾部。P已看到110,后续正确读却仍是100,就无法给已成功的加10找到一个保留效果的合法顺序;本地fsync不够。相反,11已经提交而成功响应丢失时,该逻辑操作可以仍处于pending,后来的合法读取看到110并不矛盾,历史completion应按需要保留它,而不是因超时就把它删除。

还有另一种错误:同一逻辑请求的两份尝试进入11和12,两项日志都正确提交并各加10。复制安全完全成立,但逻辑请求若只应代表一次加10,余额120就违约。会话与结果去重把两份尝试映到同一个逻辑操作;只有第一次有效应用改变余额,后面的槽位只推进应用位置并重发原结果。该逻辑调用区间从第一次发起延续到最终取到对应结果,超时重试不是未经说明的新业务调用;若API选择每次重试都算新调用,所声明的规格就不同。

因此客户端观察到某次尝试成功与服务保证“一个逻辑请求一次效果”不能混淆。持久身份、结果保留、会话过期与恢复协议决定后者成立多久、哪些请求可以安全续试。确定性状态机也只能约束自己的原子域;对外发消息应记录为事务发件箱意图,再单独报告外部效果的确认状态。

状态、位置与结果的统一恢复 ​

对已应用位置a,服务恢复状态应是同一已提交前缀的业务状态、请求结果表和应用索引。只恢复余额却丢去重,或只恢复位置却漏业务,都可能在一次重试后破坏刚才的历史映射。快照安装把截点索引/任期、结果表和配置一并发布;联合配置让提交证据跨成员变更继承。固定配置证明不自动涵盖这两项机制。

完整服务任务要求逐项填出三副本日志、commitIndex、lastApplied、业务结果与客户端知识;其检查器只复算有限事件,不替代本页的历史构造或Raft安全证明。

参考资料
  • Fred B. Schneider, “Implementing Fault-Tolerant Services Using the State Machine Approach,” 1990.
  • Leslie Lamport, “The Part-Time Parliament,” 1998.
关系图谱12 个相邻概念 · 3 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组
类型化关系