Ohhnews

分类导航

$ cd ..
foojay原文

Temporal之于代码,犹如数据库之于数据

#temporal#持久执行#工作流#数据库#可靠性

曾几何时,应用程序自己管理数据文件。每个程序都自创一套锁定机制、崩溃恢复和一致性保障。每个程序都用自己独特的方式把事情搞得一团糟。

然后关系数据库出现了,它向应用开发者做出承诺:你声明你要什么,我保证它存活下来。原子性、持久性、崩溃恢复——所有这些都从应用代码中移出,进入一个专用平台。如今没人再自己写基于文件的事务日志了。

现在,让我们看看我们是如何处理执行的……

前数据库时代,重演一次

如果你写代码(Java或其他语言)足够久,你至少构建过同一个系统:一张满是状态标志的表、一个扫描卡住记录的计划任务、一个重试计数器列,以及一个每两周就有人问“订单48291怎么还卡在 PROCESSING?”的 Slack 频道。

一个涉及信用卡扣款、库存预留、邮件通知和发货调用的支付流程,通常会被实现为一套手搓的烂摊子:用 Kafka 主题或 SQS 队列传递消息,用 Quartz 或 cron 调度重试,在 Postgres 里放一个 status 列追踪进度,到处写防御性代码问“这一步是不是已经跑过了?”

这就是前数据库时代在执行层面的重演。每个团队都在构建自己的针对运行中程序的崩溃恢复,而每个团队都用自己独特的方式把它搞砸。

现在这个问题有了名字:持久化执行(Durable Execution)。Temporal.io 是一个正在定义该品类的平台。它的承诺和数据库是一样的,只是面向不同的层次:你编写普通的(Java)代码,平台保证它运行到完成,无论崩溃、部署还是故障。

Temporal.io 之于代码执行,正如数据库之于数据。

预写日志,但针对你的局部变量

数据库能扛住崩溃是因为预写日志(WAL):每次变更在应用之前先被记录,崩溃后数据库重放日志,重建状态。

Temporal 工作流以同样的方式扛住崩溃:Temporal 工作流生命周期中每一个有意义的事件(Activity 启动、定时器触发、结果返回)都会被持久化为事件历史。

当运行 Temporal 工作流的 JVM 挂掉时,Temporal 会将那段历史重放至你的代码,从而在另一个 Worker 上重新水合该工作流。你的局部变量、你在循环中的位置、你当前处于第 3 步(共 7 步)——所有这些都会被重建。

你的 int retriesRemaining 能扛住 JVM 崩溃,就像已提交的行能扛住 Postgres 重启一样。这也是为什么 Temporal 工作流代码必须是确定性的——重放只在代码每次都走相同路径时才有效。这是事件溯源所要求的纪律,但应用到了程序本身。

跨度数月的“事务”

一个 ACID 事务能在毫秒级别给你“要么全做,要么全不做”。而一个 Temporal 工作流能在任意时间跨度内给你“运行到完成”。你可以这样写:

Workflow.sleep(Duration.ofDays(30));
sendRenewalReminder(customer);

不会有任何线程、Pod 或 JVM 空等一个月。Temporal 工作流是被持久化“停放”的,几乎不消耗资源,当定时器触发时会被重新水合——即使你在此期间部署了四十次、替换了每一台服务器。

实际上,一个 Temporal 工作流就是一个可以跨越数天或数月的事务。它并非字面上的 ACID(长时间运行的业务流程使用 Saga 和补偿机制而不是回滚),但其保证的方向是一致的:流程要么完成,要么从它精确停止的地方恢复执行。

[LOADING...]

那些困扰分布式系统的“半完成、卡在中间状态”的问题,从此不再是你的烦恼。

同一模式,在技术栈的其它层面

  • 虚拟内存。 操作系统让你假装拥有比实际物理内存更大的 RAM,在背后进行页面换入换出。Temporal 让你假装你的进程永远活着,将执行本身在 Temporal Worker 之间换入换出。Temporal 工作流就是虚拟进程
  • 垃圾回收。 JVM 把手动内存管理(以及一整类 bug)从你手中接走。Temporal 以同样的方式把手动故障管理从你手中接走。你不再需要编写重试循环,就像你不再需要写 free() 一样。
  • Kubernetes。 K8s 让基础设施变得声明式和自愈:“保持三个副本运行”是一个系统会不断趋近的目标。Temporal 为函数做了同样的事:“这段代码运行到完成”是一个平台会不断趋近的目标,无论中间有多少 Temporal Worker 死亡。

在每一种情况下,一个困难且容易出错、每个应用都在重新实现的问题(数据持久性、内存、编排、执行)都被移入一个专用平台,平台返回一个保证。

[LOADING...]

这对你的 Java 代码意味着什么

具体来说,采用 Temporal 意味着你可以删除这些东西:

  • 状态标志列以及扫描它们的清理任务
  • 包裹在每个外部调用周围的重试逻辑
  • 用于问“这个是不是已经跑过了?”的幂等记账代码
  • 仅仅为了在故障中存活而存在的队列加 cron 管道

剩下的代码读起来就像它实现的业务流程本身:扣款、预留库存、发邮件、等待发货、30 天后提醒客户。

[LOADING...]

直来直去的 Java,而可靠性工程则被一个专门做这件事的平台承接了。

几十年前我们就不再自己写事务日志了。再过几年,用队列、cron 和状态标志手搓执行恢复的做法,会看起来同样过时。

你的数据库让你的数据持久化。Temporal 为你的代码做了同样的事。


文章 Temporal Is to Your Code What a Database Is to Your Data 首发于 foojay