Workflows 与 Dynamic Workflows:概念、使用方式与适用场景

This article is extracted from the chat log with AI. Please identify it with caution.

说明:本文由 Codex 根据作者提供的主题、对话素材与 Cloudflare 官方文档辅助生成,属于 AI 生成/整理内容,非作者原创。请读者自行甄别并交叉验证。

Cloudflare 这里有三个很容易混在一起的概念:WorkflowsDynamic WorkersDynamic Workflows。它们并不是三个同级、可互相替换的产品。

  • Workflows:把部署时已经确定的业务代码,作为可持久化、可重试、可等待的多步骤流程运行。
  • Dynamic Workers:由一个已部署的 Worker 在运行时加载另一段代码,并在受控沙箱中执行;它本身不提供跨调用的工作流持久化。
  • Dynamic Workflows:用 @cloudflare/dynamic-workflows 把两者组合起来,让“运行时加载的、按租户或按任务变化的代码”也获得 Workflows 的持久步骤、等待、重试和恢复能力。

因此,面对“订单、审批、ETL 该怎么可靠地跑”的问题,通常从 Workflows 开始;面对“用户或 AI 生成了不同的自动化代码,且每段代码都要可靠地跑几天”的问题,才需要 Dynamic Workflows。本文按 2026-08-07 的官方文档整理,价格、限额和预览功能会变化,应以文末链接为准。

一张表先分清边界#

能力WorkflowsDynamic WorkersDynamic Workflows
代码何时确定部署 Worker 时请求/任务运行时运行时
主要目标可靠编排多步骤业务过程安全执行动态、AI 生成或用户上传的代码可靠执行动态代码定义的多步骤过程
跨 sleep / 故障后的状态内建持久化不保证,不能依赖同一 isolate由 Workflows 持久化
典型入口WorkflowEntrypoint.run()env.LOADER.load() / get()Dynamic Worker 中的 env.WORKFLOWS.create()
典型使用者应用后端、数据管道、AI 流程沙箱、代码模式、预览、可编程平台多租户自动化平台、可恢复的 AI Agent
首选风险控制幂等、细粒度步骤、重试与补偿最小权限 binding、网络出口控制、资源上限同时具备左两列控制,并增加代码版本与租户路由控制

还要避免另一个名称相近的误解:Dynamic Workers / Dynamic Workflows 不是 Workers for Platforms 的 Dynamic Dispatch。后者面向已部署 Worker 的多租户路由;前者是运行时加载代码。官方限制页也明确指出,Workflows 不能部署在 Workers for Platforms namespace 中。

1. Cloudflare Workflows:持久化执行,而不是“后台开个异步函数”#

Workflows 是 Workers 平台上的 Durable Execution(持久化执行) 模型。一个 Workflow 实例由多个步骤组成:成功步骤的结果由平台保存;后续步骤失败、Worker 重启或流程休眠后,平台会从已成功的步骤继续,而不是把整个流程从头再跑。

这特别适合以下过程:上传文件后进行 AI 处理并等待人工审批、定时发送试用到期提醒、批量同步第三方 API、等待支付 webhook、或者具有补偿动作的 Saga 流程。官方将其用于 AI 应用、数据管道、用户生命周期与 human-in-the-loop 审批等场景。Workflows 概览

运行时模型#

HTTP / Queue / Cron / CLI / REST API
      Workflow binding.create(params)
   独立 Workflow instance(ID、输入、状态、步骤历史)
 WorkflowEntrypoint.run(event, step)
       ├─ step.do()           可持久化、可重试的业务步骤
       ├─ step.sleep()        定时暂停
       ├─ step.sleepUntil()   等到绝对时间
       └─ step.waitForEvent() 等 webhook / 人工输入

几个核心对象如下:

  • WorkflowEntrypoint:代码中的流程类,继承它并实现 run(event, step)
  • Workflow instance:一次独立运行;有唯一 ID、输入事件、状态、步骤结果与保留期。
  • WorkflowEvent:包含只读 payload、创建时间、实例 ID、Workflow 名;若由 Workflow 自身的 cron schedule 创建,还会带 schedule 元数据。
  • WorkflowStep:持久化边界。step.do() 成功后的可序列化返回值会被保存并在恢复时复用;run() 的最终返回值可从 Dashboard、Workers API 或 REST API 读取。Workers API

“持久化”不等于分布式事务,也不自动提供 exactly-once 外部副作用。一个步骤可能因网络中断而重试;如果第三方已成功扣款、但响应在传回前丢失,下一次尝试仍可能再发起调用。因此幂等键、先查后写和补偿逻辑仍是业务代码的职责。

2. Workflows 的核心 API 与最小实现#

定义与配置#

下面是一个“订单等待付款确认”的最小骨架。示例刻意把数据库读取、外部调用和等待拆成不同步骤:这样每一步拥有独立的持久化、超时与重试边界。

import {
  WorkflowEntrypoint,
  type WorkflowEvent,
  type WorkflowStep,
} from "cloudflare:workers";

type OrderParams = { orderId: string };

export class OrderWorkflow extends WorkflowEntrypoint<Env, OrderParams> {
  async run(event: WorkflowEvent<OrderParams>, step: WorkflowStep) {
    const order = await step.do("load order", async () => {
      // 返回值必须可序列化;大对象建议写入 R2/D1 后返回引用。
      return loadOrder(event.payload.orderId);
    });

    await step.sleep("payment grace period", "1 hour");

    const approval = await step.waitForEvent<{ paid: boolean }>(
      "wait for payment webhook",
      { type: "payment-confirmed", timeout: "24 hours" },
    );

    if (!approval.payload?.paid) {
      return { orderId: order.id, status: "unpaid" };
    }

    return await step.do(
      "fulfill order",
      {
        retries: { limit: 5, delay: "10 seconds", backoff: "exponential" },
        timeout: "10 minutes",
      },
      async () => fulfillOrder(order),
    );
  }
}

上例之所以会等待,是代码显式调用了 step.waitForEvent();它不是 Workflows 默认要求前端逐步点击“下一步”。常规流程由 run() 中已部署的 await step.do()、条件、循环和 try...catch 自动推进,step.sleep() / step.sleepUntil() 到时也会自动恢复。只有付款 webhook、人工审批或用户决定这类业务关卡,才由前端或外部系统调用受认证的后端 API,再由后端以 instance.sendEvent({ type, payload }) 放行。pause() / resume() 用于运维式暂停和恢复,不能传递“同意/拒绝”等业务数据;详见 Workflows:实例、步骤、事件与恢复 API 实战

wrangler.jsonc 中声明 Workflow binding:

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "name": "orders",
  "main": "src/index.ts",
  "compatibility_date": "2026-08-07",
  "observability": { "enabled": true },
  "workflows": [
    {
      "name": "order-workflow",
      "binding": "ORDER_WORKFLOW",
      "class_name": "OrderWorkflow"
    }
  ]
}

class_name 必须匹配导出的类名,binding 则是普通 Worker 中通过 env.ORDER_WORKFLOW 访问的变量名。若希望直接按计划触发,可以在同一项上添加 schedules: ["0 * * * *"];每次匹配 cron 时,平台会创建实例,无需另写顶层 scheduled handler。构建第一个 Workflow 触发 Workflows

步骤、等待、事件和补偿#

API作用实践要点
step.do(name, [config], callback)执行业务步骤并持久化返回结果可定义 retries、退避方式和每次尝试的 timeout;适合一次外部 API 调用、一次数据库查询或一次存储读写。
step.sleep(name, duration)按相对时间暂停支持毫秒或 "1 hour""7 days" 一类时长。
step.sleepUntil(name, date)暂停到绝对时间适合“下周一 09:00 UTC 再执行”。
step.waitForEvent(name, { type, timeout })等待 webhook、人工批准或外部系统回调type 要与发送方一致;超时会抛错,允许超时时继续时要 try/catch
instance.sendEvent({ type, payload })向指定实例投递事件事件即使在流程抵达等待点之前送达,也会被缓冲到匹配的等待步骤。
rollback handlerstep.do() 注册反向补偿操作适合库存预留、扣款、发票等 Saga;它不是数据库全局事务。

step.do() 当前默认配置为 retries.limit: 5(总尝试次数)、10 秒初始延迟、指数退避和 10 分钟单次尝试超时;策略可改为 constantlinearexponential。对验证失败、无效凭证这类不应重试的错误,可使用 NonRetryableError 结束重试。睡眠与重试 事件与参数

外部 webhook 的接收端一般只负责认证、定位 instance 并投递事件:

const instance = await env.ORDER_WORKFLOW.get(instanceId);
await instance.sendEvent({
  type: "payment-confirmed",
  payload: { paid: true },
});

也可以从 HTTP handler、Queue consumer、Cron handler、Durable Object、Wrangler CLI、Dashboard 或 REST API 创建实例。父 Workflow 可以创建子 Workflow,但子流程创建成功后会独立运行,父流程不会自动等待它结束。触发 Workflows

实例生命周期#

通过 binding 的 create() 创建实例,通过 get(id) 获取实例,再调用 status()pause()resume()terminate()restart() 管理它。常见状态包括 queuedrunningwaitingpausedcompleteerroredterminated。如果终止时希望触发已注册的补偿处理器,可使用 terminate({ rollback: true })触发与实例管理

3. Workflows 的正确使用方式:六条比 API 更重要的规则#

  1. 步骤要小且有业务边界。 不要把“读数据库 → 调 API → 写数据库 → 发邮件”塞进一个 step.do();独立调用应拆步骤,才能独立重试、超时和观察。
  2. 步骤要幂等。 对扣款、发信、创建资源等副作用,使用调用方提供的 idempotency key,或在步骤中“先查是否已完成,再执行”。网络失败并不表示第三方未提交。
  3. 跨步骤状态只能来自输入或步骤返回值。 休眠后内存会丢失,不能依赖模块变量、修改过的数组或被改写的 event.payload。将需要的状态作为 step.do() 的返回值保存。
  4. 步骤名和流程分支应可确定。 步骤名相当于状态缓存键;不要在名字中使用 Date.now()、随机数,也不要在步骤外用随机数或当前时间决定走哪条路径。若在循环中建步骤,循环数据必须来自稳定的前置步骤输出。
  5. 把副作用放入步骤内。 Workflow 在恢复时可能重新执行步骤外的普通代码;步骤外的日志、创建实例或网络请求可能重复。
  6. 大数据放外部存储。 非流式步骤结果和事件 payload 都有 1 MiB 限制。用 R2/D1/KV 保存大数据,只在步骤结果中返回对象 key、主键或 checksum;JavaScript Workflow 若确实要保存较大的二进制结果,可返回新的、未锁定的 ReadableStream<Uint8Array>

这些规则决定 Workflow 是否真的可靠,尤其是第 2 条。官方规则文档明确说明:流程会 hibernate,非步骤内存状态会失效,步骤名必须稳定,外部调用应具备幂等性。Rules of Workflows

4. Dynamic Workers:运行时加载代码的沙箱原语#

Dynamic Workers 让一个“宿主 Worker(Worker Loader)”在运行时提供 modules 形式的代码,创建并调用另一个 Worker。它适合 AI Code Mode、用户上传的小应用、预览环境、一次性自动化和自定义工具等场景。Cloudflare 将它定位为容器的轻量替代方案:主系统决定动态代码拥有的 binding、网络访问和资源上限。Dynamic Workers 概览

在宿主 Worker 的配置中,首先声明 worker_loaders binding:

{
  "worker_loaders": [{ "binding": "LOADER" }]
}

随后使用两种加载方式:

  • env.LOADER.load(code):每次创建新的 Dynamic Worker,适合一次性、每次代码都不同的执行。
  • env.LOADER.get(id, callback):以 ID 尝试复用已加载的代码,适合同一应用或同一租户的多次请求。它是缓存,不是“固定进程”:相同 ID 后续仍可能由新 isolate 执行,callback 也可能再次被调用。

get() 的 ID 必须对应不可变的代码与配置。同一 ID 的 callback 应始终返回相同内容;只要代码、依赖、权限或配置变化,就换一个版本 ID,例如 tenant-42:workflow:v17 或内容 hash。反过来,若每次都用 load() 或随机 ID,会牺牲复用效果并增加按唯一 Dynamic Worker 计费的数量。Dynamic Workers 入门 Worker Loader API

最小权限比“能执行不可信代码”更重要#

Dynamic Worker 的隔离不是把安全设计自动完成。特别需要注意:如果没有显式设置,globalOutbound 会继承宿主 Worker 的网络能力,通常意味着可访问公共互联网。对于用户或 AI 生成的代码,推荐默认禁止网络,再以很小的受控能力集逐项开放。以下片段位于宿主的 fetch(request, env, ctx) 中:

const worker = env.LOADER.get(`tenant:${tenantId}:v${version}`, async () => ({
  compatibilityDate: "2026-08-07",
  mainModule: "index.js",
  modules: { "index.js": await loadBundledCode(tenantId, version) },

  // 默认切断 fetch() / connect();动态代码只能使用明确授予的能力。
  globalOutbound: null,

  env: {
    // 由宿主实现的 RPC capability;密钥保留在宿主的 env / props 中。
    CRM: ctx.exports.TenantCrm({ props: { tenantId } }),
  },
}));

return worker.getEntrypoint().fetch(request);

这里 CRM 应是宿主 Worker 导出的 WorkerEntrypoint/Service Binding。动态代码只能调用允许的方法,不能读取 props 中的 token;宿主可以在方法中做鉴权、租户隔离、参数校验、审计和凭证注入。若确实要允许 HTTP,则将 globalOutbound 指向宿主实现的网关,对域名 allowlist、请求体、速率与凭证进行控制,而不是直接放开网络。Dynamic Worker bindings 出口控制

还应配合以下约束:

  • limits: { cpuMs, subRequests } 可按动态 Worker 或 getEntrypoint() 调用设置,达到任一上限会抛出异常。
  • 通过 tails 附加 Tail Worker,捕获动态代码的日志、异常与请求元数据;日志应带 worker / tenant ID,且不能记录密钥或原始敏感 payload。
  • Dynamic Worker 直接接收的是 JavaScript / CommonJS / Python 模块。TypeScript 和 npm 依赖要先转译、解析并 bundle,例如使用 @cloudflare/worker-bundler;官方也提示 Python 启动通常比 JavaScript 慢。
  • 如果动态代码需要持久状态,但不需要流程编排,可以考虑 Durable Object Facets:宿主 DO 充当 supervisor,动态 class 作为 facet,二者拥有隔离的 SQLite 存储。

自定义资源上限 可观测性 Durable Object Facets

5. Dynamic Workflows:让动态代码也获得耐久步骤#

Dynamic Workflow 不是另一个独立的编排服务,而是 Dynamic Workers + Workflows 的适配层。@cloudflare/dynamic-workflows 负责把“某个 Workflow 实例属于哪个动态代码版本”的路由信息持久化,并在 sleep、等待事件或 isolate 回收后重新载入正确的 Dynamic Worker。

flowchart LR
  R["请求:tenantId + codeVersion"] --> L["Worker Loader:已部署的宿主代码"]
  L --> D["Dynamic Worker:按租户和版本加载"]
  L --> B["wrapWorkflowBinding:写入路由 metadata"]
  B --> D
  D --> I["WORKFLOWS.create():创建实例"]
  I --> E["Workflows Engine:持久步骤、重试、sleep、事件"]
  E --> X["DynamicWorkflow 入口"]
  X --> L
  L --> D

对应关系可以拆成三部分:

  1. Worker Loader 是部署的固定代码。它认证请求、确定租户与代码版本、从存储取 bundle,并授予受控 bindings。
  2. Dynamic Worker 是按租户或任务加载的代码。它可以像普通 Workflow 一样写 step.do()step.sleep()step.waitForEvent()
  3. DynamicWorkflow 入口类 由库创建,供 Workflows Engine 调用。引擎恢复某实例时,库读取实例 metadata,要求宿主重新加载相应 Dynamic Worker,并取回其中的 Workflow class 继续执行。

官方流程的关键点是 wrapWorkflowBindingcreateDynamicWorkflowEntrypoint:前者把 metadata 附着到动态代码创建的每个实例上,后者在恢复时用 metadata 路由回对应的动态 Worker。Dynamic Workflows 官方指南

配置与实现骨架#

宿主 Worker 同时要有 Worker Loader 和一个常规 Workflow binding:

{
  "worker_loaders": [{ "binding": "LOADER" }],
  "workflows": [
    {
      "name": "dynamic-workflow",
      "binding": "WORKFLOWS",
      "class_name": "DynamicWorkflow"
    }
  ]
}

宿主端的简化代码如下。这里把 codeVersion 放进 metadata;这比仅保存 tenantId 更稳妥,因为恢复一个数天前的实例时,应载入创建时的代码版本而不是租户的“最新代码”。这是根据 Loader 的“同一 ID 必须映射相同 WorkerCode”和 Workflows 恢复时按 metadata 重载代码这两条规则得出的工程实践。

import {
  createDynamicWorkflowEntrypoint,
  DynamicWorkflowBinding,
  wrapWorkflowBinding,
  type WorkflowRunner,
} from "@cloudflare/dynamic-workflows";

// 必须 re-export;运行时据此创建供动态代码使用的包裹 binding。
export { DynamicWorkflowBinding };

function loadTenantWorkflow(env: Env, tenantId: string, codeVersion: string) {
  const workerId = `tenant:${tenantId}:workflow:${codeVersion}`;

  return env.LOADER.get(workerId, async () => ({
    compatibilityDate: "2026-08-07",
    mainModule: "index.js",
    modules: { "index.js": await fetchBundle(tenantId, codeVersion) },
    globalOutbound: null,
    env: {
      WORKFLOWS: wrapWorkflowBinding({ tenantId, codeVersion }),
      // 这里只传入经过能力收敛的 Service Bindings。
    },
  }));
}

export const DynamicWorkflow = createDynamicWorkflowEntrypoint<Env>(
  async ({ env, metadata }) => {
    const { tenantId, codeVersion } = metadata as {
      tenantId: string;
      codeVersion: string;
    };

    return loadTenantWorkflow(env, tenantId, codeVersion)
      .getEntrypoint("TenantWorkflow") as unknown as WorkflowRunner;
  },
);

动态代码自身几乎就是一个普通的 Workflow:

import { WorkflowEntrypoint } from "cloudflare:workers";

export class TenantWorkflow extends WorkflowEntrypoint {
  async run(event, step) {
    const result = await step.do("apply tenant action", async () => {
      return this.env.CRM.upsert(event.payload);
    });

    await step.sleep("wait for review", "1 day");
    const approval = await step.waitForEvent("wait for approval", {
      type: "approval",
      timeout: "7 days",
    });

    return { result, approval };
  }
}

export default {
  async fetch(request, env) {
    const instance = await env.WORKFLOWS.create({
      params: await request.json(),
    });
    return Response.json({ id: await instance.id });
  },
};

与普通 Workflows 相同,Dynamic Workflows 的 .status()、暂停、恢复、重试、休眠与等待事件语义保持不变;库增加的是“从持久化实例回到正确动态代码”的路由。不要把 secret 放进 wrapWorkflowBinding 的 metadata:metadata 会随实例事件持久化,动态代码可经由状态接口读到它。metadata 仅放 tenant ID、代码版本、路由键等非敏感信息。Dynamic Workflows 安全说明

6. 场景与选型#

需求首选能力原因
固定的订单、审批、邮件、ETL 流程Workflows代码由团队部署,可靠性需求高,模型最简单。
一段一次性 AI 生成的分析/工具代码Dynamic Workers 的 load()不需要跨步骤持久化,强调隔离与快速执行。
用户自定义自动化、每租户不同的审批链或 webhook 链Dynamic Workflows动态逻辑需要在失败、等待和多天后继续。
实时房间、单资源串行修改、WebSocket 协调Durable Objects核心是按对象 ID 的有状态协调,而非长链路编排。
高吞吐消息削峰、至少一次投递Queues,必要时再触发 WorkflowQueue 解决消息传递;Workflow 解决步骤状态与等待。
重 CPU、长时间纯计算容器或外部计算服务Workflow 的 wall-clock 可以很长,但每步骤仍受 Workers 活跃 CPU 上限约束。

Dynamic Workflows 最有价值的三类场景是:多租户 SaaS 让客户定义 onboarding / billing retry / approval 自动化;AI Agent 在运行时生成长期计划并等待人工确认;平台让客户提交数据转换、定时任务或 webhook 链,同时由平台保留重试与进度。Dynamic Workflows 使用场景

反过来,以下情况通常不值得上 Dynamic Workflows:流程逻辑固定、只有一个快速步骤、对同步 P99 延迟极敏感、没有能力治理需求,或者无法给用户生成代码建立鉴权、审计、网络控制与版本管理。它会带来代码加载、隔离、版本兼容、日志归属和成本统计的额外复杂度。

7. 限额、成本与可观测性#

下面的数字是本文日期的快照,不是容量或报价承诺:

Workflows 项目FreePaid
每步骤活跃 CPU10 ms默认 30 s,最高可配到 5 min
每步骤 wall-clock不限(仍受 CPU 约束)不限(仍受 CPU 约束)
非流式步骤结果 / event payload1 MiB1 MiB
每实例持久化状态100 MB1 GB
最大 sleep / event timeout365 天365 天
每 Workflow 最大步骤数1,024默认 10,000,最高 25,000
每账户并发运行实例10050,000
已完成实例状态保留3 天30 天

正在 sleep、等待事件或等待重试的实例处于 waiting,不占运行并发;恢复时若无空位仍会排队。对于大数据,R2 中保存对象并在 Workflow 中传递 key,通常比把结果塞进步骤状态更可靠、更便宜。Workflows limits

Workflows 目前按 Workers CPU、实例请求、持久化存储和执行步骤四个维度计费;等待 I/O、sleep 或空闲不产生 CPU 时间。官方计费页在本文整理时注明,步骤和存储计费将从 2026-08-10 开始。Dynamic Workers 则仅面向 Workers Paid,计费还包括每日创建的唯一 Dynamic Worker 数量;使用稳定版本 ID 的 get() 有助于避免反复创建同一代码版本。部署前应按最新价格页重新计算。Workflows pricing Dynamic Workers pricing

运行中至少应记录这些维度:Workflow 名称、实例 ID、步骤名与重试次数、租户 ID、动态代码版本、外部请求的 idempotency key、资源消耗和补偿结果。Workflows 可从 Dashboard、Workers API、REST API、Wrangler 与指标系统观察;Dynamic Workers 的日志要通过 Tail Worker 收集,因为它们不会自动进入宿主 Worker 的 Logs。

8. 上线前检查清单#

  • 每个 step.do() 都有明确的业务边界、超时和重试策略。
  • 所有可重复外部副作用都有幂等键、去重记录或补偿处理。
  • 跨步骤数据来自 event.payload 或步骤返回值;大数据只传引用。
  • 步骤名、循环顺序和分支条件在恢复时可确定。
  • Workflow 实例 ID、保留期、并发、步骤数和存储量符合当前计划。
  • 动态代码有不可变的版本 ID / hash;恢复中的实例不会悄悄切到“最新版本”。
  • Dynamic Worker 默认无网络;所有网络和业务能力经宿主 gateway / Service Binding 收敛。
  • 动态代码永远拿不到原始密钥;metadata、日志、错误信息都已排除敏感数据。
  • 为每个租户、代码版本和实例建立可检索日志、告警、成本与审计链路。
  • 在本地和测试环境验证重试、sleep、事件提前到达、超时、代码版本升级和补偿路径。

结论#

Workflows 解决的是“已知业务流程如何在故障与长等待中可靠继续”;Dynamic Workers 解决的是“未知或按需代码如何被隔离并受控执行”;Dynamic Workflows 则解决“动态代码如何也拥有可靠的长期执行能力”。绝大多数后端流程应先选择普通 Workflows。只有当流程定义本身属于租户、用户或 Agent,并且必须运行时加载时,再引入 Dynamic Workers 和 @cloudflare/dynamic-workflows 这层组合。

继续深入#

参考资料#

本文共 7683 字,创建于 Aug 7, 2026

相关标签: Cloud, DevOps, TypeScript, ByAI