工作流编排基础
有些任务不是一次请求就能跑完的。处理一笔订单要查库存、扣款、发邮件、更新库,中间任何一步都可能失败,失败后还要能从断点续跑而不是从头再来。Cloudflare Workflows 就是用来编排这类多步骤任务的,它运行在 Workers(Cloudflare 的边缘计算函数服务)之上,把每一步的结果自动存下来,故障重启后接着往下走。这篇讲清楚工作流编排的核心概念,以及如何定义一个 Step 并把它绑定到 Worker 上跑起来。
什么是工作流编排
工作流编排解决的是多步骤任务的可靠执行问题。普通 Worker 处理一个请求,函数跑完就结束,中间状态全部丢失。工作流不一样,它把任务拆成若干 Step,每个 Step 执行完的结果会被持久化,整个流程可以跑几分钟、几小时甚至几天。
两种执行模型对比
| 对比项 | 普通 Worker 请求 | Workflows 工作流 |
|---|---|---|
| 执行时长 | 单次请求,通常秒级 | 可长达数小时或数天 |
| 状态保留 | 不保留,请求结束即丢失 | 每个 Step 结果自动持久化 |
| 失败恢复 | 整个请求重跑 | 从最后成功的 Step 续跑 |
| 适用场景 | 即时响应、简单计算 | 多步骤、长耗时、需协调的任务 |
| 触发方式 | 收到 HTTP 请求即执行 | 通过绑定创建实例触发 |
这套机制在业界叫做持久执行(Durable Execution)。核心思想是程序把自己的执行进度隐式存下来,不用你手动往外部数据库写状态。Workflows 引擎负责存取,你只管写业务逻辑。
什么时候该用工作流
| 场景 | 普通 Worker 的问题 | 工作流的好处 |
|---|---|---|
| 电商订单处理 | 扣款后崩溃,库存与支付不一致 | 每步存档,崩溃后从断点续跑 |
| 文件上传后处理 | 转码耗时过长,请求早超时 | 异步编排,可长时间运行 |
| 用户注册流程 | 发邮件失败就要整个重来 | 单独重试失败步骤 |
| 数据同步与批处理 | 中途中断无法续传 | 进度持久化,断点续跑 |
| 定时周期任务 | 需要自己管调度和状态 | 内置调度,状态自动保留 |
Step 是什么
Step 是工作流的最小执行单元。一个工作流由若干 Step 顺序排列组成,每个 Step 做一件事,做完把结果交还给引擎保存。Step 提供四个方法,覆盖了执行、等待、休眠三类操作。
Step 方法一览
| 方法 | 作用 | 是否持久化 |
|---|---|---|
| step.do | 执行一段代码并存下返回值 | 是 |
| step.sleep | 休眠一段相对时长后继续 | 是 |
| step.sleepUntil | 休眠到某个固定时刻再继续 | 是 |
| step.waitForEvent | 暂停等待外部事件到达 | 是 |
最常用的是 step.do。它接收一个名字和一个回调函数,回调的返回值会被引擎存下来。下一次工作流重启走到这个 Step,直接读存档跳过执行,不会重复跑。
判断是否该拆成独立 Step 的原则很简单,问自己一句话,这段代码失败后要不要整段重跑。如果只想重跑出问题的部分,就把它单独拆成一个 Step。
编写第一个工作流
工作流是一个继承 WorkflowEntrypoint 的类,核心是 run 方法。先看一个最简单的例子,抓取数据然后处理。
import { WorkflowEntrypoint, WorkflowStep } from "cloudflare:workers";
import type { WorkflowEvent } from "cloudflare:workers";
type Params = { name?: string };
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
// 第一步,抓取数据,结果自动持久化
const data = await step.do("fetch data", async () => {
const response = await fetch("https://api.cloudflare.com/client/v4/ips");
return await response.json();
});
// 第二步,处理数据,用上一步的结果
const result = await step.do("process data", async () => {
return {
name: event.payload.name ?? "World",
ipCount: data.result.ipv4_cidrs.length,
};
});
return result;
}
}
几个关键点
| 要素 | 说明 |
|---|---|
| WorkflowEntrypoint | 必须继承的基类,泛型是 Env 和 Params |
| run 方法 | 工作流入口,接收 event 和 step 两个参数 |
| event.payload | 触发时传入的参数,整个流程里不可变 |
| step.do 的名字 | 作为存档的键,必须确定性,不能含 Date.now 等随机值 |
| 回调返回值 | 必须可序列化,单个 Step 结果上限 1 MiB |
| run 的返回值 | 可选,会作为实例输出,可通过 status 查询 |
event 在整个工作流执行期间是不可变的。想在步骤之间传递数据,靠的是 step.do 的返回值,不要试图修改 event.payload。
Step 的名字是存档依据,名字一样就认为是同一个 Step。所以名字里不能塞随机数、时间戳这类不确定的值,否则每次重跑引擎都当成新 Step,存档就失效了。
绑定配置
工作流类写好后,要在 wrangler(Cloudflare 的命令行工具)配置文件里声明它,Worker 才能通过绑定访问。配置写在 wrangler.toml 里。
name = "my-workflow"
main = "src/index.ts"
compatibility_date = "2026-08-14"
[observability]
enabled = true
[[workflows]]
name = "my-workflow"
binding = "MY_WORKFLOW"
class_name = "MyWorkflow"
字段含义
| 字段 | 作用 |
|---|---|
| name | 工作流名称,部署后全局标识 |
| binding | 代码里访问的变量名,对应 env.MY_WORKFLOW |
| class_name | 必须和导出的类名一致 |
class_name 对不上会报错,binding 写什么就在代码里用什么。一个 Worker 可以声明多个工作流,每个 [[workflows]] 块对应一个。
工作流内部还能访问其他绑定,比如 D1(Cloudflare 的无服务器 SQLite 数据库)、KV(Cloudflare 的键值存储)、R2(Cloudflare 的对象存储服务),通过 this.env 拿到,和普通 Worker 一样。
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
const order = await step.do("load order", async () => {
// 通过 this.env 访问 D1 绑定
const row = await this.env.DB
.prepare("SELECT * FROM orders WHERE id = ?")
.bind(event.payload.orderId)
.first();
return row;
});
return order;
}
}
触发与查询实例
工作流配置好之后,需要通过绑定创建实例来触发执行。通常是在 fetch 处理函数里调用 create。
export { MyWorkflow } from "./workflow";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const instanceId = url.searchParams.get("instanceId");
// 传了 instanceId 就查询状态
if (instanceId) {
const instance = await env.MY_WORKFLOW.get(instanceId);
return Response.json(await instance.status());
}
// 否则创建新实例,可传入参数
const body = await request.json() as { name?: string };
const instance = await env.MY_WORKFLOW.create({
params: { name: body.name },
});
return Response.json({ instanceId: instance.id });
},
} satisfies ExportedHandler<Env>;
实例操作方法
| 方法 | 作用 |
|---|---|
| create | 创建并启动一个新实例 |
| get | 按 ID 取回实例引用 |
| instance.id | 实例唯一标识 |
| instance.status | 查询执行状态和各步结果 |
create 接收可选参数对象,params 会作为 event.payload 传进 run。不传 params 也能跑,payload 就是空。create 返回的实例对象上有 id,拿这个 id 后续查询进度。
本地开发调试
wrangler dev 启动本地环境,工作流会在本地模拟执行。
npx wrangler dev
启动后用 curl 触发实例
curl http://localhost:8787 -H "Content-Type: application/json" -d '{"name":"测试"}'
返回的 instanceId 用来查状态
curl "http://localhost:8787?instanceId=实例ID"
命令行还能直接查实例详情,看每一步的执行情况
npx wrangler workflows instances list my-workflow
npx wrangler workflows instances describe my-workflow 实例ID
instances describe 的输出会列出每个 Step 的状态、返回值、重试次数、报错信息,调试时非常有用。
小结
工作流编排把多步骤任务拆成可独立重试的 Step,每个 Step 的结果自动持久化,故障后从断点续跑。基础套路就三步,写一个继承 WorkflowEntrypoint 的类用 step.do 定义步骤,在 wrangler.toml 里用 [[workflows]] 声明绑定,在 fetch 处理函数里调用 create 触发实例。下一篇讲状态如何持久化以及出错后怎么重试和超时控制。
下一篇 状态与错误处理