摘要

  • RFC 2372 建议:发送 PREPARED 前先建立 prepared-recovery record;在 Prepared 状态发送 COMMIT 前先建立 commit-recovery record,并一直保留,直到出现文档点名的解除证据。
  • 这种次序让协议声明依赖本地持久记忆,却不能自行证明字节已经落到稳定介质、故障后能够重放、事务身份能够正确恢复,或发起应用最终知道了结果。

分布式事务里最值得警惕的词,未必是 COMMIT,而可能是 PREPARED。

COMMIT 听起来像结局,人们自然会追问它究竟承诺了什么。PREPARED 却容易被当成过程中的一句回话:参与方已经完成准备,协调者稍后决定,流程还会继续。但从属方一旦把 PREPARED 发出去,就越过了一条不能凭本地意愿退回的线。套接字断开、进程重启,都不再允许它把事务当作从未存在。它已经承诺保存足够的状态,等待并执行将来的决定。

1998 年 7 月发布的 RFC 2372,用恢复记录的先后次序把这份承诺显形。它是一份 Informational RFC,不是互联网标准;文档也明确把协议动作与日志动作之间的映射称为建议,而非最终定义。这种克制必须保留。它没有报告某个产品的部署,没有指定存储引擎,没有给出断电实验,更没有证明一次恢复成功。但它留下了一条基础设施原则:软件若要宣布一种在故障之后仍有约束力的状态,重建该状态所需的证据必须先进入保管。

PREPARED 改变了谁还拥有选择权

TIP 采用 presumed-abort 的两阶段提交。参与方准备之前,故障可以把事务导向中止;参与方准备之后,单方面退出就不再安全。上级方可能已经收齐所有投票并决定提交。因此,从属方必须穿过让网络对话消失的故障,仍然记得这笔事务。

RFC 2372 对次序写得很具体。从属方在发送 PREPARED 之前,应建立 prepared-recovery record;在收到 ABORT、COMMIT 或 QUERIEDNOTFOUND 之前,应保留该记录;只要该记录还存在,就不应发送 COMMITTED 或 NOTRECONNECTED。这不是为了缓存一条报文,而是为了保存一项尚未解除的责任。

上级方承担另一段责任。它先收到 PREPARED,然后在 Prepared 状态发送 COMMIT 之前建立 commit-recovery record;直到收到 COMMITTED 或 NOTRECONNECTED,才可移除记录。也就是说,它可能需要在恢复后重发的决定,必须先拥有一份能够在本地重现的证据。

这比“把重要事件写日志”更严格。PREPARED 把参与方从本地自由转入外部约束;COMMIT 把上级方从考虑转入必须能够重复的决定。删除记录也不是普通清理,而是在宣称:文档规定的证据已经结束了这项义务。

连接从来不是记忆本身

TIP 把应用对话与事务协调分成两条通道。业务请求可以通过 HTTP 等应用机制传输,事务管理器则在另一条通道交换命令。这种“两条管道”让事务协议能够复用,却也意味着连接不能充当全部历史。

故障之后,上级方可以使用从属事务标识符发送 RECONNECT;必要时,这个标识符要从事务日志恢复。从属方如果仍保留 Prepared 状态,就回答 RECONNECTED;如果已经收到 COMMIT、回答过 COMMITTED 并遗忘该事务,就回答 NOTRECONNECTED。后一个否定答复并不必然表示失败,在规定的序列中,它可能恰好证明从属方已经完成并释放状态。

从属方也可以向上级方发出 QUERY。QUERIEDEXISTS 表示上级方仍认识这笔事务,随后会重新连接;QUERIEDNOTFOUND 则允许从属方中止,因为上级方可能发送过 ABORT,却没收到 ABORTED,随后依 presumed-abort 规则遗忘了事务。看似相同的“找不到”,只有放回角色、状态与消息历史中才有意义。

因此,TCP 重新连通从来不等于恢复。传输层能让两个端点再次交流,却不能重建正确的事务标识,不能判断哪一侧仍负有义务,也不能证明缺少记录究竟意味着“安全结束”“推定中止”还是“证据丢失”。恢复依靠的是本地持久身份与远端协议证据的结合。

删除凭证并不对称

prepared record 与 commit record 没有一个通用的关闭信号。从属方等 ABORT、COMMIT 或 QUERIEDNOTFOUND;上级方等 COMMITTED 或 NOTRECONNECTED。两组信号反映的是两个角色不同的认知位置。

对从属方而言,权威决定,或证明上级方已不再持有事务的查询结果,可以解除准备义务。对上级方而言,参与方的确认,或在参与方完成并遗忘之后无法重连的特定结果,可以结束重复 COMMIT 的需要。文档没有允许因为计时器到期、进程恢复、磁盘紧张或操作员觉得“对端大概完成了”而删除证据。

超时仍有作用。RFC 2372 预期本地事务超时会限制资源长期不可用,并帮助解决死锁。但释放资源不等于证明结果正确,经过一段时间也不会自然转化成恢复凭证。资源结局、协议结局与恢复后的知识,仍是三个不同事实。

书面次序不能证明落盘次序

“建立恢复记录”落到存储栈里,立刻会产生更多问题。所谓建立,是写入用户态缓冲区,进入内核页缓存,提交日志,刷新到介质,复制到另一个故障域,还是拿到具有持久语义的确认?记录会不会撕裂?故障后如果丢了易失索引,还能不能找到对应的事务标识?异步发送器会不会抢在日志真正稳定之前把 PREPARED 发出去?

RFC 2372 没有回答这些实现问题,也没有声称回答。它提供要求与补充信息,却没有指定数据库、发布一致性测试、注入断电或测量重建过程。因此,恢复记录是一项有待实现与验证的义务,不是某个实现已经合格的证据。

用 Heng Lu 的“运行代码优先”视角看,文本可以规定正确软件必须维护的顺序,运行证据则从更晚处开始:观察持久写入,在最不利的时刻终止进程,重新启动,重建事务身份,与对端对账,并核验本地资源结果。RFC 给出了实验应验证的结构,却没有替任何实现写下“通过”。

删除同样如此。实现可能确实等到规定消息后才移除记录,但如果删除先于资源管理器对应状态持久化,依然会错。另一个系统也可能安全却无限期地保留陈旧记录,造成资源与运维负担。正确性不在一个名字像日志的文件里,而在协议状态、日志状态与资源状态之间可验证的关系中。

委托转移责任,没有消灭责任

RFC 2372 区分轻量客户端与功能更完整的服务器。客户端可以把事务协调委托给服务器,从而不需要自己的事务日志。客户端的持久机制因此减少,恢复责任却没有消失,而是移到接受该角色的系统。

“客户端不需要日志”很容易被误读成“系统不需要持久恢复证据”。正确解释是:客户端不再是这份协议状态证据的保管者。审计必须跟随义务去服务器寻找答案:它以哪个标识建立了什么记录,在说出哪句话之前写入,又在收到哪一种凭证后删除。

委托也没有自动解决调用者的知识问题。客户端应用调用 commit 以后,可能收不到最后答复,而事务其实已经完成。RFC 2372 把确认最终结果的责任留给应用,并提到实现特定的用户日志可能有帮助。因此,事务管理器可以成功恢复协议状态,发起业务的人或服务却仍不知道最终结局。

这也划清了本文与 RFC 2371 既有文章的边界。协议定义已经说明 TIP 的 COMMIT 回答不自动等同于业务收据;RFC 2372 更独特的贡献,是暴露恢复之下的本地证据链:参与方的 prepared record、上级方的 commit record、故障后找回的事务身份,以及允许每份记录消失的特定远端信号。

两条管道要求应用自己维持顺序

应用流量与 TIP 命令分离,制造了另一种排序风险。事务管理器未必看得见应用是否仍有请求在途。因此 RFC 2372 要求应用遵守规则:有关联请求尚未完成时不要调用 commit;在本地事务管理器登记事务之前,也不要对合作方的事务请求作肯定答复。

再完美的事务日志,也无法修复违反这种序列化的应用。完全持久的 commit record 可能只是在保存一项针对不完整业务操作集合的决定;完整重建的 TIP 状态,也可能对应一条过早离开的应用回复。各层必须一起检查,却不能被当成同一层。

这正是“现实层”问题的具体形式:应用请求、本地资源动作、TIP 状态、日志记录、存储确认、网络消息、恢复出的标识、对端答复、本地最终结果和客户端所知结论,层层相连却并不等同。上一层可以为真,下一层仍可能未知。把它们压缩成一个“已提交”,制造确信的速度会快过制造证据。

安全可以认证对端,却不能证明整笔事务

RFC 2372 把应用认证与授权留在 TIP 之外。它关注的安全问题更窄:未经授权的一方可能发命令,或冒充受信任的事务管理器。TLS 可以认证端点并加密 TIP 命令,但是否使用要经过协商。

即使 TLS 通道正确认证,也不能证明对端已经持久写入恢复记录,不能证明应用请求获得授权,不能证明资源管理器执行了正确操作,也不能证明后续业务系统承认结果。端点身份保护了证据链的一条边,却不能替代本地持久性与应用权限。

恢复时,这种分离尤其重要。重新连接的一方既需要可信通道,也需要正确事务身份。安全认证的错误事务依然错误;来自冒充者的正确标识同样危险。安全证据、状态证据和业务授权必须互补。

最小初始规范,把未来决定留在本地

RFC 2372 没有规定通用日志格式、存储引擎、事务 API 或运维控制台。互操作需要共享命令、角色、标识和恢复含义,并不需要所有参与者使用同一种数据库。

借用 Heng Lu 的“最小初始规范与局部未来决定”视角,这是一条有意保持局部性的边界。RFC 定义了两套系统在对话中断后能够和解所需的共同规则;每个实现仍可选择如何让记录持久、如何将它绑定到资源状态、如何向应用暴露歧义,以及如何测试故障路径。自由与举证责任由此同时落到实现者身上。

所以,厂商或运营者不能只指向规范来回答恢复问题。真正有用的问题是经验性的:消息发出前记录是否已经持久?断电后能否存在?失去易失索引后能否重建标识?究竟哪条答复允许删除?删除是否与本地完成保持正确次序?客户端丢掉最终响应后能否查明结局?

今天的分布式系统仍会在故障前说出带有承诺的词:队列说已接受,存储说已复制,编排器说已就绪,支付服务说已授权。每个词都暗含“什么会在故障后留下”。RFC 2372 的历史教训不是“只要有日志,话就是真的”,而是:任何在故障之后仍有后果的状态声明,都需要一份可检查、可持久、次序正确的证据对象站在它身后。

来源