多步骤业务流程中断后的幂等性处理方案:通过事件驱动架构(Kafka等)配合幂等设计解决步骤重复执行问题。
一个进程启动了一个多步骤任务。步骤 1 验证客户身份。步骤 2 创建内部记录。步骤 3 转账、更改订阅、向合作伙伴发送不可逆请求,或者执行你的业务认为严肃的任何操作。然后进程挂了。
容器被 kill 了。部署在滚动更新。宿主机消失了。worker 在执行到一半时重启了。
现在问题简单而可怕:
再次运行时会发生什么?
如果步骤 3 已经发生了,从步骤 1 重试可能会重复执行昂贵的操作。如果跳过太多步骤,客户可能处于半激活状态。如果盲目重试,外部提供商可能接受同一请求两次。
这就是很多金融科技工作的形态。账户开户、订阅激活、信贷放款、还款结算、KYC 刷新、定时扫账、账本过账。它们不是一笔数据库事务。它们是伪装成函数调用的小型业务流程。
在一家美洲的金融科技公司,公开的架构故事是事件驱动的:Kafka、服务、异步处理、通过事件保持一致性,以及围绕幂等性的仔细思考。这种风格在银行规模的环境中是有意义的。
但这项工作枯燥的部分从来不只是"发布一个事件"。真正的工作是决定每个步骤意味着什么,哪条消息是真相来源,如何识别重复,如何让消费者可以安全地重放,以及如何对已经跨越边界的操作进行补偿。
最终你会有 saga、状态机、幂等性 key、outbox 表、重试 topic、对账任务、审计跟踪、仪表盘、告警和运行手册。
这不是批评。在严肃的金融系统中,这种纪律是工作的一部分。你不能对钱说"队列是最终一致的"然后就去吃午饭。
但拥有这套机制是有运营成本的。每个团队都必须记住同样的失败模式。每个新的工作流都必须重建同样的脚手架。每次事故都要问:步骤完成了吗,事件发布了吗,重试是否重复了,客户的当前状态是真实的吗?
痛苦的是,大多数这些机制并不是产品特定的。
补偿逻辑是产品特定的。步骤边界是产品特定的。决定转账可以重试但合作伙伴调用需要幂等性 key 是产品特定的。但"在 worker 崩溃后记住已完成的步骤 3"是基础设施。
而基础设施最终会成为产品。
这就是为什么像 DBOS 和 Temporal 这样的框架值得关注,即使你明天不会采用其中任何一个。
Temporal 的模型围绕 workflow 和 activity 构建。Temporal 服务为每个 workflow 执行存储持久化的事件历史。当 worker 崩溃或重启时,workflow 代码可以从历史中重放,已完成的 activity 不会简单重复。服务有事件发生的记录。
DBOS 采用了不同的产品形态。DBOS Transact 被定位为一个在你应用内部运行的开源库。目前 DBOS 网站仍显示 Transact 使用你现有的 Postgres 数据库来存储和恢复 workflow 状态与执行历史。他们的付费模式围绕 DBOS Conductor——工具、支持和服务托管选项,Pro 和 Teams 计划的定价基于 checkpoint 使用量。
这个差异很重要。
Temporal 要求你运行或付费使用一个 workflow 服务。Temporal Cloud 定价基于消费,主要围绕 action 和存储。当前最低 Cloud 计划标价 100 美元/月。
DBOS 正在做一个更 Postgres 原生的赌注。开源库让你在应用代码中获得持久化 workflow,而 Conductor 添加了监控、恢复、版本控制、告警和支持等运维工具。
有趣的是,两者打包了同样的曾经属于内部的机制:
这个多步骤任务在进程死亡后存活。
这句话过去意味着定制化的平台工作。现在可以从一个框架开始。
这里有一个陷阱,正是每次基础设施变好时都会出现的陷阱。
人们混淆了"框架记住发生了什么"和"系统现在知道应该发生什么"。
如果一个 workflow 向客户收费、激活订阅、配置访问权限、发送电子邮件并更新分析,框架可以帮助使这个 workflow 可恢复。它可以记录步骤完成、重试 activity、显示执行在哪里停止、使历史可见而不是埋在日志里。
但它不能告诉你向客户收费应该发生在激活之前还是之后。
它不能告诉你一次失败的激活应该触发退款、重试、人工审核还是宽限期。它不能仅仅因为一个合作伙伴 API 接受幂等性 key 就告诉你它实际上是幂等的。它不能告诉你你的"步骤"是一个业务操作还是三个业务操作穿着马甲。
那仍然是设计工作。
在我现在合作的一个海湾地区金融科技公司,这出现在正常的产品 workflow 中。定时扫账有趣的地方不是定时器触发了。它有趣是因为系统需要知道考虑了哪些账户、跳过了哪些、外部调用成功了哪些,以及在部署后进程唤醒时应该发生什么。
持久化执行在那里帮助很大。记录在 Postgres 中的已完成步骤比一行日志和祈祷要好得多。workflow 控制台比 SSH 进 worker 寻找线索要好得多。但有用的对话向上转移了。
团队不再问"我们如何构建重试表?",而是问"在哪里retry 变成 compensation 的边界?"。团队不再问"崩溃后如何恢复?",而是问"对这个客户来说,恢复意味着什么?"
这是对工程时间更好的利用。
在任何持久化执行的宣传中,有一件事我需要谨慎——幂等性。
在框架边界内,你可能获得非常强的保证。完成的 workflow 步骤可以被记录。activity 结果可以被记住。重试可以被控制。执行可以在崩溃后恢复。
在外部边界,现实更混乱。
支付提供商、银行合作伙伴、电子邮件系统、身份供应商和卡处理器对重复请求都有各自的想法。有些很好地支持幂等性。有些在文档中支持它。有些直到它们的副作用和你的响应之间发生超时才支持它。
边界调用仍然需要稳定的请求标识符、提供商引用和对账。你仍然需要知道超时意味着"什么都没发生"、"发生了什么但你没看到"还是"稍后检查"。
持久化执行减少了那种逻辑渗透进来的地方。
它没有废除分布式系统。
我喜欢这个类别的原因是它符合软件中不断重复的一种模式。最初,可靠性属性是手艺。强大的团队因为别无选择而在内部构建它。然后足够多的团队遇到了同样的痛苦,属性变成了产品。
可观测性是这样做的。功能开关是这样做的。密钥管理是这样做的。CI/CD 是这样做的。策略即代码是这样做的。现在持久化执行也在这样做。
枯燥的控制平面属性变成了你可以购买、安装或外包的东西。
这是好事。我不想念每个团队偶然构建自己的迷你 workflow 引擎。
但工作没有消失。它转移了。
价值不再在于证明你的 worker 能在重启后存活。价值在于决定哪些 workflow 值得持久化、从业务角度看哪些步骤是原子的、哪些失败应该重试、哪些失败应该补偿、以及谁拥有客户结果。
这就是持久化执行变得有趣的地方。
不是因为 DBOS 或 Temporal 让失败消失了。
而是因为它们让失败足够可见,以至于我们不用再假装困难的部分是重试循环。
Temporal Event History 文档
Temporal Cloud 定价文档
为了测试我的项目,我使用 Railway。如果你想要 20 美元开始,请使用这个链接。
对于进一步的操作,你可以考虑屏蔽这个人或举报滥用。