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