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