第 9 章:把结果未知当作需要继续调查的事实
一个工具调用返回错误,应用最容易想到的动作是再试一次。但是错误通常描述的是客户端观察到的通信结果。请求是否到达、远程事务是否提交、回执是否丢失,需要不同证据。未知状态为这些差异留下位置。
9.1 相同报错可以对应不同业务事实
假设客户端看到连接断开。断开可能发生在发送之前,也可能发生在工具提交之后。若调用协议没有提供可验证的执行阶段,客户端无法仅凭错误文本区分两者。
随书工具故意选择一个确定位置制造故障:先完成 PostgreSQL 工具事务,再关闭 HTTP 连接,不把成功响应交给 worker。工具账本有一项效果,worker 只能记录 unknown。这个实验把“报错”和“已生效”放在同一次真实运行里。
worker 发送原操作键
工具提交积分效果
工具丢弃响应并断开连接
worker 记录 unknown
核查者按原键查询工具
匹配回执后补齐 succeeded
这里没有把网络断开当作数据库回滚。HTTP 请求的生命周期与工具事务的生命周期重叠,却并不相同。调用方取消等待,也不能自动撤销远程已经提交的业务。
9.2 过期领取同样先进入未知
进程崩溃后可能连错误处理都没有机会运行。恢复器只看到一项已经超过租约的 running 任务,不知道它在工具调用之前还是之后停止。因此,示例先把这类领取转入 unknown,推进围栏并清理旧拥有者。
随后依据工具合同进行核查。已找到匹配效果,可以保存成功;权威查询暂时没有结果,且工具支持稳定键、任务未取消、预算仍允许,才把原计划按原键重排;工具不支持查询或幂等,则保留人工核查责任。
即使某次测试知道进程死在调用之前,生产恢复器也没有自动获得这份测试屏障知识。不要把测试控制器知道的事实,偷偷当成线上执行内核已经持久保存的证据。这是验证故障恢复时非常容易越过的一条边界。
9.3 查询结果必须连接回原意图
核查时需要验证操作键、计划指纹、账户、数量和回执身份。仅仅发现“这个账户今天增加过积分”,不足以说明增加来自本次任务。多个工单可能对同一账户产生相同数量,按模糊字段匹配容易造成错误归因。
查询成功还需要绑定核查开始时的工作流世代。另一位核查者可能已经写入终态,或者操作者在查询期间请求了取消。示例使用条件变更,避免一份迟到查询把已经改变的状态重新覆盖。
取消不应阻止承认已经存在的效果。工具已经产生匹配结果时,保存成功可以同时保留 cancel_requested,让操作者知道取消到达得太晚。相反,查询为空而取消已经成立时,不应再把任务排回执行队列。
9.4 查询为空不是脱离合同的无效果证明
一个权威接口返回 404,在当前实验里表示相同工具账本没有这个键。真实系统可能还有延迟副本、查询缓存、异步写入或正在执行但尚未提交的请求。必须把查询语义和重放能力一起判断。
对支持相同键去重的工具,即使原请求随后提交,新旧调用仍应在工具账本中收敛到同一个效果。对不支持这项能力的工具,一次查询为空往往不足以批准重发。它可能只能触发稍后核查或人工调查。
本书没有用“等待够久”自动把 unknown 变成 failed。时间可以触发调查、告警或人工升级,但除非工具合同明确提供终结条件,时间经过本身没有告诉我们远程世界发生了什么。
9.5 预算耗尽后仍然需要核查
最大尝试次数限制新的执行机会。第三次领取在工具生效后崩溃,仍然可能通过查询确认成功。不能因为预算已经用完,就把任务强行改为业务失败并忽略已经发生的效果。
若预算耗尽而查询为空,示例保留 unknown 并要求人工处理,不继续自动重排。这样,重试限制和事实核查各自承担责任:前者约束成本与风险,后者说明业务当前已知结果。
操作界面应该展示未知原因、原业务身份、最后已知阶段、尝试预算和核查记录。把 unknown 显示成一个普通红色失败,然后给出醒目的“重新创建任务”按钮,会在界面层诱导用户绕过原操作键。
练习。 工具查询接口走只读副本,写入后平均延迟两秒可见。第一次调用超时,核查立即得到 404。请分别讨论工具支持稳定幂等键和完全不支持幂等键时,系统允许采取的下一步,并指出哪些结论需要额外证据。