持久化执行
当一个 worker 正在执行工作流中途时,其容器被回收。另一个 worker 接手该运行,从触发器开始遍历图谱,对于每个输出已在运行日志中的步骤,跳过执行并复用缓存输出。它在日志中尚未存在的第一个步骤处停下并执行该步骤。这一机制涵盖了崩溃、部署、多日暂停和重试。
运行日志
每个工作流运行都有一个运行日志:一个单一的压缩检查点文件,保存了在全新 worker 上恢复运行所需的一切。
包含内容:
- 每个已完成的步骤对应一个条目,以步骤名称为键:输入(机密已遮蔽)、输出、状态、持续时间和失败步骤的错误信息
- 循环迭代和路由分支以相同结构记录,嵌套在其父步骤下
- 运行级别的标签
写入时机:
- 运行开始时,在第一个步骤执行之前写入一次
- 执行期间每 15 秒写入一次,由后台循环对自上次写入以来已完成的内容进行快照
- 在最终状态(成功、失败或暂停)时写入一次
每次写入覆盖之前的副本。仅保留最新的检查点,文件在上传前会进行压缩。
重放和跳过
恢复不是特殊路径。每次 worker 开始执行一个运行时,它从触发器开始遍历工作流图谱,在每个步骤处询问:这个步骤的输出是否已在日志中?
- 如果是,且步骤已完成(
SUCCEEDED或PAUSED),引擎返回缓存输出并继续前进 - 如果否,引擎执行该步骤,记录其输出,然后继续
第一次调度运行时日志为空,因此每个步骤都会执行。恢复后日志中填充到中断点为止,因此引擎快速跳过所有已完成的步骤,仅执行接下来未完成的步骤。
sequenceDiagram
participant Worker
participant Log as 运行日志
participant Flow as 工作流图谱
Worker->>Log: 加载检查点
Worker->>Flow: 从触发器开始遍历
Flow-->>Worker: 步骤 A
Worker->>Log: 是否有 A 的输出?
Log-->>Worker: 是(已缓存)
Note over Worker: 跳过 A,复用输出
Flow-->>Worker: 步骤 B
Worker->>Log: 是否有 B 的输出?
Log-->>Worker: 是(已缓存)
Note over Worker: 跳过 B,复用输出
Flow-->>Worker: 步骤 C
Worker->>Log: 是否有 C 的输出?
Log-->>Worker: 否
Note over Worker: 执行 C,追加到日志
在突发崩溃时最坏情况下的数据丢失是 worker 死亡时正在执行的单个步骤。它会从最后一个检查点重新运行;之前的所有内容都被跳过。
触发恢复的因素
每种中断都通过相同的重放路径解决;只有触发器不同。
- Worker 崩溃或部署。队列将运行重新分配给另一个 worker,它加载日志并重放。
- 步骤暂停。Piece 创建一个等待点。当等待点触发时,一个恢复作业入队,worker 重放该运行。
- 从失败步骤重试。复用相同的日志;运行重新入队,worker 从失败点重放。
- 同一 worker 内的正常推进。相同的重放模型,无需离开进程。