第 14 / 23 篇

第 13 章:版本升级必须解释旧任务

一个任务可能在周一获得批准,周三才进入执行。如果周二已经发布了新 worker,任务经历的就不只是进程切换,还包括程序语义切换。数据库能保留一份 JSON,却不能保证新程序对它的解释与原审批者相同。版本兼容要回答的是:眼前这份历史意图,当前执行者有没有能力按原合同继续。

13.1 给计划版本一个具体含义

随书计划保存 version,工作流还保存独立的 plan_version。当前可执行版本为 1,动作固定为 issue_credit,数量使用最小单位,币种限定为 CNY。这些限制共同界定了旧 worker 理解的合同。添加一个可忽略的展示字段,与把数量单位从分改成元,是完全不同的升级。

如果新版本把 amount_minor=500 解释成另一种单位,即使 JSON 仍能成功解码,也不能认为兼容。兼容性属于业务解释,不属于语法解析。类似地,改变账户命名规则、修改币种默认值、把一个工具调用拆成两个副作用,都会改变审批与恢复的前提。

本实验没有实现自动迁移框架。它采用一个清楚的最低策略:领取时检查持久计划版本,版本不被当前 worker 支持,就把任务置为 incompatible,写事件,不调用工具。这个状态保留了“需要有能力的执行者处理”的事实,避免用重试掩盖解释失败。

13.2 真实实验怎样制造未来版本

TestPersistedVersionTwoRejectedByOldWorker 先建立并批准一个合法任务,然后在隔离数据库里显式把计划版本和持久版本改成 2。这是用于测试兼容边界的 SQL 注入,不是应用提供的编辑计划接口。随后启动真正的旧版本 worker 子进程,让它读同一张表。

本次通过的集成实验记录了以下观察值,字段顺序在展示时作了整理:

{"persisted_version":2,"worker_version":1,
 "state":"incompatible","post_calls":0}

其中 post_calls=0 来自真实 HTTP 工具服务的计数。测试还检查 attempt=0,说明系统没有先调用一次再发现错误。拒绝发生在领取事务所管理的执行边界里,而不只是某个界面上显示一条警告。

这项实验没有证明任意历史版本都能平滑升级。它证明当前 worker 在所构造的版本冲突下停止执行。生产升级还需要覆盖合法旧版本能否继续、回滚之后新记录怎样处理,以及混合版本执行者同时在线时的路由策略。

13.3 指纹不能被迁移悄悄覆盖

审批绑定的是计划指纹。改变账户、数量或者幂等能力,再覆盖原计划和指纹,会让“已经批准”的文字继续存在,却失去原来批准的对象。数据库里有一名审批人的名字,并不足以证明他同意迁移后的动作。

需要改变业务语义时,应建立新的计划修订,明确它与旧任务的关系,再执行相应审批。若迁移只改变存储布局,也要证明迁移前后对应同一业务意图,并保留迁移记录与校验结果。本书代码没有这个修订模型,因此不提供“直接编辑计划后继续执行”的捷径。

幂等键同样属于兼容合同。实验根据租户和业务操作标识生成稳定键,恢复期间不因换进程或换 fence 而改变。若升级随手修改键的编码,就可能让工具把同一操作当成新动作。字段名称看似是实现细节,落到工具账本里就可能成为业务身份。

13.4 不能只升级成功路径

部署清单通常关注 API 能否启动、数据库迁移能否完成。持久任务还要求检查 ready、running 和 unknown 的历史记录。一个新 worker 可能能执行新计划,却无法解释旧工具回执;或者能读回执,却错误地把旧版权威查询的 404 解释为可重放。

因此升级演练应从故障窗口开始:旧进程在工具提交之后退出,新进程能否只凭已保存的计划、键和工具合同核查?再反过来:新进程写下未知结果之后回滚,旧进程能否诚实拒绝不认识的记录?不能因为最终成功的样本都能读取,就推导恢复过程兼容。

本实验的工具合同由受信任应用提供,尚未单独保存提供者能力版本、去重保留期限和查询协议版本。真实接入时应将这些信息纳入兼容评审。不能假定一个长期保留的 idempotent=true 可以替代下游合同的变更管理。

13.5 用隔离队列处理不可兼容任务

incompatible 不是业务失败,也不是工具明确拒绝。它说明当前执行程序无权作出进一步解释。操作界面应把它送到版本处置队列,显示受影响版本、创建时间和所需执行器,而不是反复重排到普通队列制造告警。

安全的处置可以是恢复一组仍受支持的旧 worker,或者经评审把任务迁移成一个新的审批修订。无论采用哪一种,都需要避免两个版本同时认为自己拥有同一任务。行锁、状态条件和 fence 仍然适用,版本路由不应绕过它们。

练习。 版本 2 想给计划增加 reason,同时把单次额度从 10000 提高到 50000。请分别判断旧 worker 读取这两项变化时能否继续执行,并设计一次“工具已经提交、随后回滚版本”的升级演练。答案应明确审批指纹、操作键、工具回执和不可兼容状态各自怎样处理。