跨系统发布的一致性:幂等、版本锁、不可变快照与 Saga

少于 1 分钟阅读时长

发布时间:

当一次发布同时涉及本地数据库、远端服务和另一个运行环境时,本地事务无法覆盖全部副作用。最危险的情况不是请求直接失败,而是远端已经成功、本地却没来得及记录结果。

为什么不能持有长事务等待 HTTP

在数据库事务内调用远端接口会长时间占用行锁和连接池。进程崩溃后无法判断远端结果,远端超时也不等于远端失败,盲目重试可能产生重复副作用。

因此可将流程拆成三个阶段:

  1. Prepare: 本地短事务,校验版本并抢占发布权;
  2. Publish: 事务外调用远端系统;
  3. Finalize: 本地短事务,记录成功或失败。

这是一种轻量 Saga。它不把所有系统伪装成原子事务,而是让每个中间状态都可识别、可恢复。

request_id 与 expected_revision

request_id 回答“这是不是同一次业务请求”,用于重复提交幂等;expected_revision 回答“用户操作的是否仍是当前版本”,用于阻止旧页面覆盖新数据。

接口应先检查请求号是否存在,存在则返回已有结果;不存在时通过版本与状态条件抢占 PUBLISHING。同一对象的并发请求只允许一个进入发布阶段。

草稿与正式快照分离

审批新版本时直接覆盖线上记录会让旧版本提前消失。更稳健的模型是:草稿表保存最新可变候选,正式表保存不可变快照。新版本处理中,旧 ACTIVE 快照继续生效;新快照 Finalize 成功后,再在本地事务中将旧版本标记为 SUPERSEDED。失败快照保留 FAILED 状态以供审计和重试。

解耦不同业务状态机

内容审批通过和已投放运行系统并不是同一个状态,应分别维护内容状态与投放状态。投放失败只更新投放状态,不回滚已生效内容。用户可以看到内容仍然可用,同时存在一项待恢复投放任务。

处理双环境部分成功

双环境发布通常先体验、后正式。体验环境失败时不继续正式环境;体验成功而正式失败时整体记为失败,重试使用相同请求号与固定内容版本。若远端成功而本地 Finalize 失败,应进入可核对状态,通过 compare/reconcile 恢复,而不是盲目重复发布。

通知和分析埋点等旁路能力可以 fail-open,通过 Outbox、队列或重放补偿;正式版本、权限和运行配置应 fail-safe。

分布式一致性的重点不是消灭失败,而是把失败变成有限、可观察、可重试的状态。只要系统能够回答哪一步成功、当前使用哪个版本、下一步如何恢复,部分成功就不再是一场事故。