摘要

  • Argus 的 guardian 封装进程、对象与恢复责任;原子 action 则让参与其中的可恢复对象变更具有可串行化、全有或全无的结果。
  • commit 并不证明外部设备已经动作、人工流程已经完成或未受管理的 I/O 已经撤销,也不证明 handler 背后的自然人身份与业务授权。

提交灯亮了,机器却没有动

设想一个 handler:它先更新持久控制对象,再要求 Argus 管理范围之外的设备执行动作。topaction 得到一致决定,所有参与的 guardian 都同意,稳定对象的新版本写入存储,调用者收到成功返回。

设备仍然静止。

这并不与事务结果矛盾。Argus 对加入 action 的可恢复对象作出了很强的承诺;对没有加入的外部设备,它没有作出承诺。“commit”听起来像终局,因此人们很容易把它误读成整个业务已经完成。但在 Barbara Liskov 领导的 Argus 研究中,终局不是一种语气,而是语言与运行时精确定义的范围。

1983 年的论文《Guardians and Actions》由 Liskov 与 Robert Scheifler 合著。guardian 把某个节点上属于分布式程序的资源、对象与进程组织在一个恢复域中;action 把一组应当串行出现、并且要么共同生效要么恢复旧状态的操作组织在一起。两种抽象相互配合,让故障处理不必散落在应用程序的临时约定中。

与此同时,它们也划出了证据边界。commit 在参与者集合内部有效;一旦跨出集合,就需要另一张收据。

guardian 管理恢复域,而不是管理整个世界

guardian 很像一个生命周期长于单个进程的模块。它可以持有对象、运行进程,并向其他 guardian 暴露 handler。节点故障后,可恢复状态能够重建,易失状态则重新初始化。于是,系统可以明确回答:哪一组数据由谁负责恢复。

“guardian”这个词容易让今天的读者联想到全能安全监控器,那并不准确。它封装的是程序资源与恢复责任,不会自动覆盖组织周围触及的每个文件、设备、人员或服务。

Argus 的存储语义把这条界线说得很清楚。stable 变量构成恢复 resilient object 的根。在 topaction 提交前,被修改的稳定对象新状态必须写入稳定存储。volatile 变量则在 guardian 恢复时重新初始化。把一个非 resilient 对象挂到 stable 变量下面,并不会凭空让它获得事务持久性;对象类型与恢复实现仍然决定保证是什么。

因此,“Argus 让分布式程序可靠”只是粗略概括。更准确的说法是:在明确的故障假设下,它让明确的一类状态可恢复。真正产生保证的是这个定义。

一次 handler 调用会长出 action 树

在 Argus 中,远程工作不是事务之外看不见的岔路。调用另一个 guardian 的 handler 会产生 action 结构:调用方拥有 call action,目标端运行 activation action。参数与结果按值跨越 guardian 边界,返回路径也参与决定这些 subaction 如何结束。

普通的 return 或 signal 通常让 handler activation 提交;abort returnabort signal 则让它撤销。这个细节很重要:异常并不天然等于事务失败。如果应用认为某类异常不应让外层工作提交,就必须在合适的 action 边界内捕获并处理。

subaction 提供了局部隔离:一个分支可以撤销,而不必让父 action 一起失败。但当 subaction 提交时,它的暂存版本与锁会向父 action 继承,结果仍取决于父 action,最终取决于 topaction。“subaction 已提交”并不意味着它已经独立永久化,只意味着该分支成功把结果并入了父 action。

topaction 是根。它提交时,系统协调参与的 guardian,在分布式决定需要时执行 two-phase commit,并记录本 action 与已提交后代修改的稳定对象版本。topaction 若撤销,受到事务管理的状态回到此前版本。

这个层级能阻止三种夸大:handler activation 成功不等于 topaction 已提交;subaction 提交不等于独立持久;topaction 提交也不等于非参与者已经完成。

orphan 说明:调用返回并不是完整事实

分布式调用的失败往往含糊。节点消失时,另一处的工作可能仍在执行;调用者也可能只失去了返回路径,而不知道远端 activation 是否停止。Argus 把祖先已经死亡、或结果再也无法送回祖先的 action 称为 orphan。

orphan 机制不只是给超时换一个更强硬的名字。系统要确保 orphan 最终撤销,并且在合法祖先消失后不会观察到不一致的原子数据。手册还保留了一个不舒服但关键的事实:调用方得到 unavailable 时,远程活动仍可能以 orphan 形式运行。

因此,两种常见推论都不安全。没有收到结果,不等于远端什么也没执行;action 系统内的强制 abort,也不等于外部副作用被收回。orphan 的一致性保证依赖 action 通过 atomic data 通信。已经发出的电机脉冲、已打印的纸张、交给外部服务器的邮件或已经告知人的指令,不会因 Argus 祖先消失而自动倒转。

Argus 通过界定自己能控制的状态解决了一个困难问题,而不是假装整个世界都能参与事务。

open nesting 是被明确选择的例外

普通 subaction 即使提交,仍然受父 action 约束。Argus 还提供 nested topaction:它能够独立提交,即使外层 action 以后撤销,结果仍可保留。这就是 open nesting。

这类能力适合那些应当独立留存的副作用,例如一条有意持久化的协调记录。但它改变了证明内容。程序员必须确认该独立效果在父 action 失败后仍然安全,并满足相应的串行化约束。父 action 后来撤销时,nested topaction 留下结果不是原子性的神秘泄漏,而是主动划定的回滚例外。

今天的 outbox、saga 步骤或独立审计写入会再次遇到类似结构。术语可以变化,纪律不能变化:独立提交的效果必须被单独标明,不能藏进“父操作已无残留回滚”的笼统报告里。

commit 解决状态,不解决身份与目的

Argus 的 action identity 用于关联并发控制与恢复工作,它不是自然人认证。handler 可以知道参数、结果和 action 祖先,却不知道是哪位员工、客户或负责人提供了业务授权。

同样,可串行化也不等于目的限制。两个请求在对象接口看来可以同样有效,但只有一个拥有获批的变更单。一次全有或全无的更新可以维护对象内部不变量,同时违反横跨支付网络、物理流程或组织权限的规则。这不是 action 模型的缺陷,而是提醒我们不要向模型提出它从未承诺回答的问题。

正确的运行记录应当分层。类似 Argus 的事务记录说明哪些 guardian 与可恢复对象参与、subaction 如何结束、最终决定为何、什么写入稳定存储。身份记录说明哪个执行主体,以及必要时哪个自然人发起或批准操作。外部收据说明另一服务、设备或人工流程是否完成。跨域不变量则必须观察所有相关控制面。

署名本身也有边界

Liskov 是自然的叙事入口:她领导研究项目,也撰写了 1988 年的成熟综述。但 Argus 是集体成果。《Guardians and Actions》的作者是 Barbara Liskov 与 Robert Scheifler,并感谢 Argus 设计组,特别是 Maurice Herlihy、Paul Johnson 与 William Weihl。1987 年参考手册署名 Liskov、Mark Day、Herlihy、Johnson、Gary Leavens、Scheifler 与 Weihl。实现论文则由 Liskov、Dorothy Curtis、Johnson 和 Scheifler 合著。

保留这些名字不是礼仪动作。语言构想、恢复协议、参考手册与可运行实现之间的界线,本来就是技术史的一部分。正如 commit 不应吸收整个世界的结果,一位知名研究者的名字也不应吸收整个系统的工作。

来源