为 agentic 时代重新设计 Workflows 控制平面
原文:Rearchitecting the Workflows control plane for the agentic era / 2026-04-15 Source: https://blog.cloudflare.com/workflows-v2/
最初构建 Workflows——我们用于多步骤应用的可靠执行引擎——时,它面向的世界是工作流由人类行为触发,例如用户注册或下单。对于像 onboarding 流这样的用例,工作流只需支持每人一个实例——而人点击的速度终究有限。
随着时间推移,我们实际看到的是工作负载和访问模式上的量化转变:人触发的工作流变少了,机器速度创建的、agent 触发的工作流变多了。
随着 agent 成为持久且自治的基础设施,代表用户运行数小时或数天,它们需要一个可靠的、异步的执行引擎来承担正在做的工作。Workflows 正提供这个:每一步都可独立重试,工作流可以暂停等待 human-in-the-loop 审批,每个实例都能挺过失败而不丢失进度。
更进一步,工作流本身也被用来实现 agent 循环,作为管理并保活 agent 的可靠脚手架。我们的 Agents SDK 集成 加速了这一点,让 agent 容易拉起 workflow 实例并实时获得进度。一个 agent 会话现在可以触发数十个工作流,许多 agent 并发运行意味着几秒内创建数千个实例。随着 Project Think 上线,我们预计速度只会更快。
为了帮助开发者在 Workflows 上扩展他们的 agent 和应用,我们激动地宣布我们现在支持:
-
50,000 并发实例(并行运行的工作流执行数),最初为 4,500
-
每账户 300 实例/秒创建速率,之前为 100
-
每个工作流 200 万排队实例(指已创建或唤醒、正等待并发槽的实例),从 100 万上调
我们基于使用数据和基本原理重新设计了 Workflows 控制平面以支持这些提升。控制平面 V1 中,单个 Durable Object(DO)可以充当整个账户的中央注册和协调器。V2 我们构建了两个新组件以帮助系统横向扩展并缓解 V1 引入的瓶颈,然后在不中断现网流量的情况下把所有客户无缝迁移到新版本。
V1:Workflows 的初始架构
正如我们在 公测博客 中所述,我们完全在自家开发者平台上构建了 Workflows。从根本上,workflow 就是一系列可靠步骤,每一步可独立重试,可以执行任务、等待外部事件,或休眠到预定时间。
export class MyWorkflow extends WorkflowEntrypoint {
async run(event, step) {
const data = await step.do("fetch-data", async () => {
return fetchFromAPI();
});
const approval = await step.waitForEvent("approval", {
type: "approval",
timeout: "24 hours",
});
await step.do("process-and-save", async () => {
return store(transform(data));
});
}
}
为了触发每个实例、执行其逻辑、存储其元数据,我们利用基于 SQLite 的 Durable Objects,它是分布式系统中协调与存储的简单但强大的原语。
在控制平面里,有些 Durable Objects——比如 Engine,它实际执行 workflow 实例,包括其 step、retry 和 sleep 逻辑——按 1:1 比例为每个实例拉起。另一边,Account 是账户级的 Durable Object,管理该账户的所有 workflow 和 workflow 实例。
要了解 V1 控制平面的更多内容,请参阅我们的 Workflows 发布博客。
把 Workflows 推入 beta 后,我们高兴地看到客户快速扩展他们对产品的使用,但我们也意识到使用单个 Durable Object 存储所有账户级信息引入了瓶颈。许多客户每分钟需要创建并执行数百乃至数千个 Workflow 实例,这很快会压垮我们最初架构里的 Account。最初的限额——4,500 并发槽和每 10 秒 100 次实例创建——是这个限制的产物。
在 V1 控制平面上,这些限额是硬上限。所有依赖 Account 的操作,包括 create、update 和 list,都必须通过那一个 DO。高并发工作负载的用户可能在任何时刻有数千个实例在启动和结束,叠加成对 Account 每秒数千个请求。为解决这个问题,我们重新设计了 workflow 控制平面,使其可以横向扩展到更高的并发与创建速率限额。
V2:用横向扩展承载更高吞吐
为了新版本,我们以“为高量级 workflow 优化“为目标,从头重新思考了每一个操作。最终,Workflows 应该可扩展到开发者所需的任何规模——无论是每秒创建数千个实例,还是同时运行数百万个实例。我们也希望确保 V2 允许灵活的限额,我们可以切换并继续提高,而不是 V1 那种硬上限。许多设计迭代后,我们为新架构定下以下支柱:
-
一个给定实例存在的“事实来源“应该且仅应该是它的 Engine。
-
在 V1 控制平面架构中,我们在把实例入队前没有检查它的 Engine 是否真的存在。这允许出现一种坏状态:一个实例已被入队,而它对应的 Engine 还没拉起。
-
实例生命周期与存活机制必须按工作流横向可扩展,并跨多个区域分布。
-
-
新的 Account 单例应只存储最少必要的元数据,并保证有一个不变的最大并发请求数。
V2 控制平面有两个新的关键组件让我们能改善 Workflows 的可扩展性:SousChef 和 Gatekeeper。第一个组件 SousChef 是 Account 的“二把手“。回想一下,以前 Account 管理一个账户内所有 workflow 跨所有实例的元数据和生命周期。引入 SousChef 是为了在一个账户内的某个工作流内,跟踪 一个子集 的实例的元数据与生命周期。在一个账户内,一群 SousChefs 可以以更高效、更可管理的方式向 Account 汇报。(这种设计还有一个附加好处:我们不仅本来就有按账户隔离,还无意中获得了同一账户内的“按工作流隔离“,因为每个 SousChef 只管一个特定的工作流。)
第二个组件 Gatekeeper 是一种把并发“槽“(由并发限额衍生)分发给账户内所有 SousChefs 的机制。它充当租约系统。当一个实例被创建,它被随机分配给该账户内的某个 SousChefs。然后该 SousChef 向 Account 请求触发该实例。要么槽被授予,要么实例被排队。一旦槽被授予,SousChef 就触发实例执行,并承担确保该实例不会卡住的责任。
需要 Gatekeeper 是为了确保 Engines 永不压垮它们的 Account(V1 上的紧迫风险),所以 SousChefs 和它们的 Account 之间每次通信都按周期进行,每秒一次——每个周期还会批量处理所有槽请求,确保只发起一次 JSRPC 调用。这确保实例创建速率绝不会压垮或影响最重要的组件 Account(顺便一提:如果 SousChef 数量太高,我们对调用做限速,或在不同时间段内分布到不同 SousChefs)。同时,这种周期性属性让我们能保持对较老实例的公平性,并通过众多 SousChefs 确保 max-min 公平,让它们都能进展。例如,如果一个实例被唤醒,它应该比一个新创建的实例优先获得槽,但每个 SousChef 也确保它自己的实例不会卡住。
这个架构更分布式,因此更可扩展。现在,当一个实例被创建,请求路径是:
-
检查控制平面版本
-
检查该位置是否有 workflow 与版本细节的缓存版本
- 若没有,检查 Account 拿到 workflow 名、唯一 ID 和版本,并缓存这些信息
-
把仅必要的元数据(实例 payload、创建日期)存储到它自己的 Engine
那么,Engine 怎么告诉控制平面它现在存在?这在实例元数据被设置后在后台发生。由于 Durable Object 上的后台操作可能因驱逐或服务器故障而失败,我们也在创建热路径上为 Engine 设了一个“alarm“。这样,如果后台任务没完成,alarm 确保 实例会启动。
Durable Object alarm 允许 Durable Object 实例在未来某个细粒度时间被唤醒,采用 at-least-once 执行模型,内置自动重试。我们大量使用这种“任务“+ alarms 的组合,把操作从热路径上移开,同时仍然确保一切都会按计划发生。这就是我们如何让像 创建实例 这样的关键操作保持快速,而从不在可靠性上妥协。
除了解锁可扩展性,这个版本的控制平面还意味着:
-
实例列表性能更快,且与游标分页真正一致;
-
对实例的任何操作都正好做一次网络跳转(因为可以直接到它的 Engine,确保 eyeball 请求延迟尽可能小);
-
我们能确保更多实例并发地实际行为正确(按时运行)(并在不正确时纠正,确保 Engines 不会迟到继续执行)。
V1 → V2 迁移
现在我们有了能处理更高用户负载的新版本 Workflows 控制平面,我们需要做“无聊“的部分:把客户和实例迁移到新系统。在 Cloudflare 的规模下,这本身就是一个问题,所以“无聊“的部分变成了最大的挑战。在 Workflows 一周年前,它已经积累了数百万个实例和数千名客户。此外,V1 控制平面上的一些技术债意味着排队的实例可能还没有自己的 Engine Durable Object 被创建,这让事情更复杂。
这种迁移很棘手,因为客户在任何时刻都可能有实例在运行;我们需要一种方式把 SousChef 和 Gatekeeper 组件加进老账户而不造成任何中断或停机。
我们最终决定把现有的 Accounts(我们称之为 AccountOlds)迁移成像 SousChefs 一样行事。通过保留 Account DO,我们维持了实例元数据,并简单地把那个 DO 转换为一个 SousChef “DO”:
// You might be wondering what's this SousChef class? This is the SousChef DO class!
import { SousChef } from "@repo/souschef";
class AccountOld extends DurableObject {
constructor(state: DurableObjectState, env: Env) {
// We added the following snippet to the end of our AccountOld DO's
// constructor. This ensures that if we want, we can use any primitive
// that is available on SousChef DO
if (this.currentVersion === ControlPlaneVersions.SOUS_CHEFS) {
this.sousChef = new SousChef(this.ctx, this.env);
await this.sousChef.setup()
}
}
async updateInstance(params: UpdateInstanceParams) {
if (this.currentVersion === ControlPlaneVersions.SOUS_CHEFS) {
assert(this.sousChef !== undefined, 'SousChef must exist on v2');
return this.sousChef.updateInstance(params);
}
// old logic remains the same
}
@RequiresVersion<AccountOld>(ControlPlaneVersions.V1)
async getMetadata() {
// this method can only be run if
// this.currentVersion === ControlPlaneVersions.V1
}
}
我们能在 AccountOld 中实例化 SousChef 类,因为追踪实例元数据的 SQL 表在 SousChefs 与 AccountOld DO 上都是相同的。因此,我们只需决定使用哪个版本的代码。如果不是这样,我们就被迫迁移数百万实例的元数据,这会让迁移对每个账户更困难、更耗时。那么,迁移如何工作?
首先,我们准备好让 AccountOld DO 切换为像 SousChefs 一样行事(意味着发布一个包含上面代码片段版本的版本)。然后,我们按账户启用控制平面 V2,这大致同时触发以下三步:
-
所有新的实例创建请求被路由到新的 SousChefs(SousChefs 在收到第一个请求时被创建),新实例不再到 AccountOld;
-
AccountOld DO 开始把自己迁移成像 SousChefs 一样行事;
-
新的 Account DO 用对应的元数据被拉起。
所有账户迁移到新控制平面版本后,我们能在它们的实例保留期到期时让 AccountOld DO 退场。一旦所有账户上的所有 AccountOlds 实例都迁移完,我们就能永久关闭那些 DO。迁移完成时没有停机,这个过程真的感觉像在开车的时候换轮子。
试用
如果你是 Workflows 新手,试试我们的 入门指南 或 构建你的第一个可靠 agent。
如果你的用例需要比我们新默认值更高的限额——50,000 并发槽和账户级 300 实例/秒(每个 workflow 100)的创建速率限额——请通过你的 account team 或 Workers Limit Request Form 联系我们。你也可以在我们的 Discord 服务器 上反馈、提需求,或分享你如何使用 Workflows。
– 原文译于 2026-04-30