castomFLOW

值得信赖的运行

没人会注意到一个自动化,直到它把某样东西发了两次

然后所有人都会注意到。本页的四种机制之所以存在,是因为这类软件的失败总是同样四种,而它们都可以通过设计而非靠细心来避免。

发布前先模拟

把一条真实记录推入一个已关闭的工作流,然后读回原本会发生的一切:所走的路径、每一步的每个值、每条消息的措辞,以及本会发送到每个外部系统的请求。

什么都不会离开这里。不发送电子邮件、不开具发票、不尝试扣款、不写入任何行。外部调用会以意图的形式记录下来,附上本会发送的载荷,让你在它成为某人的麻烦之前就能确认映射是否正确。

模拟正是你回答那个让人不敢开启自动化的问题的方法:它到底会做什么?

模拟 · 订单 88 214未发送任何内容
  • 1校验地址匹配成功。本会写入 6 个字段。
  • ?条件订单超过 1 000 兹罗提?不——是 289,00 兹罗提。走了短路径。
  • 3发票会向开票系统发送 POST 请求。完整载荷如下。
  • !在此拦截增值税税率从错误的字段映射。在模拟中被发现,而不是在会计的收件箱里。

已发送:无已扣款:无

每次运行都锁定在其进入时的版本上

发布对工作流的更改不会影响已在其内的运行。每次运行都固定在它启动时的版本上,并按该版本完成,无论耗时多久、期间你改写了多少内容。

因此,即使一万人正进行到一半,你也可以重构工作流,而每个人都会完成当初承诺给他们的那个流程。另一种设计——在进入时就物化整条下游路径——正是大多数工具中编辑线上自动化让人感觉危险的原因。在这里,这再平常不过。

从你发布的那一刻起,新运行就使用新版本。运行所用的版本记录在运行本身上,所以九月时仍能回答关于三月行为的问题。

边运行边编辑v4 → v5
  • 1之前2 918 个运行正在进行中,全部在 v4 上。有些处于两天的等待中。
  • 2发布 v5两个步骤重新排序,一条消息重写。
  • 3之后那 2 918 个运行会以 v4 完成。从 11:40 起启动的一切都运行 v5。
  • §没有人会收到半个工作流加半个另一个工作流。

每次运行都记录版本保留旧版本

不会重复发送的重试

一个步骤是其输入的纯函数,其副作用与运行自身推进到下一步一起提交。不存在消息已发出但运行尚不知情的窗口期。

这就是全部诀窍,而且它是结构性属性而非承诺。一个在步骤中途被杀掉的工作进程——一次部署、一次崩溃、一台消失的机器——会让运行停留在原处。另一个工作进程接手并重新执行该步骤。因为副作用与推进作为一体提交,所以要么两者都发生,要么都没发生。

对支持幂等键的系统的外呼请求都会携带一个,由运行和步骤派生,使对方也能识别出这是重复请求。

工作进程在发送中途被杀已恢复
  • 1步骤 5 开始发送发货通知。
  • ✕工作进程崩溃部署。什么都没有提交。
  • 2被接手另一个工作进程发现运行仍停在步骤 5。
  • ✓只发送一次客户收到的是一封邮件,而不是两封。

副作用与推进一起提交每次运行和每个步骤一个幂等键

为人阅读而写的日志

每次运行、每个步骤、所走的分支以及决定它的值。每一步的耗时。每次外部调用的请求与响应,凭证已脱敏。运行所用的版本。运行正等待在哪里、等到何时。

当有人问起十四号下的那笔订单发生了什么时,答案是现成的,找到它只需一分钟,而不是花一下午去比对三个系统的日志。

失败的运行会集中在一处,并按原因分组,因此“一夜之间四十一次失败”会归结为“某供应商返回 503 持续九分钟”,而不是四十一次单独排查。

隔夜,已分组41 次失败
  • 1一个原因供应商 API 在 02:41 至 02:50 之间返回 503。
  • 2发生了什么全部 41 次均已重试;39 次在第二次尝试时成功。
  • 3剩余两个已停止,正在等待人工处理。两者都与失败的行一起列出。
  • §不是四十一次排查。只有一次,外加对两条记录的决定。

凭据已脱敏版本已记录

我们不承诺的事

我们不会在营销页面上公布正常运行时间数字,因为一个没有测量窗口、没有“正常”定义的数字只是装饰。如果你在合同上需要它,请告诉我们,我们会谈谈我们实际能承诺什么。

我们也无法让别人的 API 变得可靠。如果某个服务商宕机,依赖它的工作流会等待、重试,最终带着明确的原因停止。我们能承诺的是:失败是可见的,它不会损坏任何东西,而且恢复运行不会把消息发送第二次。

关于等待:等待两天的运行是运行上的一个 wake_at,而不是排队中的任务。这就是为什么一个工作流可以让十万次运行处于三周的等待中而不产生任何费用,也是为什么你不必去操心当前有多少运行在等待。

你从不去想的运行,就是成功运行的那一次。