Worker 超出 CPU 时间限制?把任务拆分来解决
你的 Worker 在小输入上运行得很好。然后你让它处理完整数据集,它跑到一半就死了,报错:
Error 1102: Worker exceeded CPU time limit你的逻辑没有问题——你撞上了单次调用的 CPU 上限。而解决办法不是申请更大的限额,而是把任务切得更小。
为什么 Cloudflare 要限制 CPU 时间
Cloudflare Workers 运行在边缘节点的共享基础设施上,所以每次调用只能获得一个有界的CPU 时间切片——也就是真正花在计算上的时间。关键在于,这不是墙钟时间:一个 Worker 可以毫无代价地await一个慢的子请求,因为等待 I/O 不消耗 CPU。消耗 CPU 的是计算——遍历数万条记录、解析巨大的负载、哈希、转换。
具体预算取决于你的套餐,并且会随时间变化(截至 2026 年,请查阅 Cloudflare 当前文档获取具体数字)。免费套餐每个请求只给很小的 CPU 预算;付费套餐给得多得多,还允许你在配置中调高上限。但每个套餐都有单次调用上限,所以任何 CPU 开销随数据增长的任务最终都会越过它——更大的套餐只是把墙推得更远而已。
共同点在于:CPU 限制保护的是 Cloudflare 共享的边缘基础设施,而不是你的批处理任务。对请求级别的任务来说它是无形的;对"处理一切"的任务来说,它是硬性停止。
解决方案:把工作拆分到多次调用中
如果工作是可分割的——批量导入、群发邮件、夜间同步、为 10 万行重建索引——你不需要一次超长调用。你需要很多次短调用,每次处理一批能在 CPU 限制内轻松完成的数据。
这个模式是一个游标(cursor)加上重复触发:
export default { async fetch(request, env) { const BATCH = 500; const cursor = Number(new URL(request.url).searchParams.get('cursor') ?? 0); const rows = await getRows(env, cursor, BATCH); // 取一小批 for (const row of rows) await processRow(env, row); // 保持在 CPU 限制内 const next = cursor + rows.length; const done = rows.length < BATCH; return Response.json({ processed: rows.length, next, done }); }, };每次调用只做有界的 CPU 工作,并返回从哪里继续。外部某个东西只需要持续调用它——推进游标——直到done为 true。那个"某个东西"就是调度器。
为什么 DIY 循环不够
最直观的做法是一个循环调用 Worker 的脚本,或者一台笔记本上的 cron 任务:
- 需要一台常开机器。本地循环会在你的笔记本休眠时死掉;用 VPS 则意味着为驱动一个边缘 Worker 而去运行一台服务器。
- 静默失败。如果某个批次出错或超时,naive 的循环要么停止(任务做一半),要么对失败视而不见、不留下任何记录。
- 没有执行历史。当积压任务卡在第 45,000 行时,你没有日志说明是哪个批次失败、为什么失败。
- 没有重试。一个瞬时子请求错误会杀死整个批次,而且没有任何重试机制。
你最终会在循环周围重建重试、日志和告警——也就是造一个调度器,却还没有它的保障。
Runhooks 如何驱动这些批次
Runhooks 是一个定时 HTTP 执行服务。排空一个大型任务只需两分钟设置:
- 创建一个任务——命名为"重建索引批次"。
- 设置 URL——你的 Worker 端点,例如
https://your-worker.workers.dev/。 - 设置调度——频率足够排空积压,例如
*/2 * * * *。 - 启用重试——失败的批次会重试,而不是让整个运行卡住。
你能得到而 DIY 循环没有的:
- 可靠的节奏——批次按计划触发,直到工作完成,不需要你自己的机器。
- 自动重试——瞬时失败会按 1 秒 → 2 秒 → 4 秒重试,而不是丢掉一个批次。
- 执行日志——每个批次都记录状态、响应和时长,你可以看着游标前进,也能发现它卡在哪里。
- 失败告警——如果批次开始失败,你会立刻收到通知,而不是事后才发现数据集只处理了一半。
把你的批次逻辑保持得足够小、始终在 CPU 限制以内,让 Runhooks 去处理"反复运行直到完成"这部分。
什么时候调度器也帮不上忙
对自己的工作负载诚实一点。拆分只有在任务是可分割时才有效。如果单个工作单元本身就太耗 CPU——一次巨大的密码学运算、一个无法分块的内存内转换、一个必须单趟完成的计算——再多的调度也没用,因为你无法通过提高调用频率来让单次调用变便宜。
对于真正不可分割的重计算,请转向Cloudflare Queues或Durable Objects来重构工作,或者把这个步骤移到一个为长任务而生的运行时上。调度器是编排可分割批次的正确工具——而不是用来延长单次昂贵计算的。
开始动手
CPU 限制不是一道你能升级越过的墙——它是在提示你把任务尺寸调整到请求级别:
- 把重任务改写成每次调用处理一个有界的批次,并带一个游标。
- 创建一个调度服务账号,按计划触发它,带重试和日志,直到积压排空。
- 用 cron 可视化工具构建并预览你的 cron 表达式。
常见问题
"Worker exceeded CPU time limit"是什么意思?
意思是你的 Worker 在单次调用中使用了超过其套餐允许的 CPU 时间,所以 Cloudflare 终止了它(Error 1102)。CPU 时间是真正花在计算上的时间——不是花在等待网络或子请求上的墙钟时间。一个 Worker 可以在 I/O 上等很久没问题,但在大数据集上的紧循环会很快烧掉 CPU 时间并触发限制。
一个 Cloudflare Worker 能获得多少 CPU 时间?
取决于你的套餐,而且限制会随时间变化——具体数字请查阅 Cloudflare 当前文档。免费套餐每次调用只允许很小的 CPU 预算,付费套餐允许得多得多,并且可以通过配置调高上限。关键点是每个套餐都有单次调用 CPU 上限,所以一个无界增长的任务无论什么套餐最终都会撞上它。
如何在不触发 CPU 限制的情况下处理大型任务?
拆分工作,让每次调用处理一个能在 CPU 限制内轻松完成的小批次,然后反复运行 Worker 直到整个数据集处理完。用一个游标(偏移量、ID 或队列)跟踪进度,这样每次运行都能从上次结束的地方继续。外部的调度服务会按计划触发 Worker、重试失败的批次,并记录每次运行,让你能看到积压正在被排空。
什么时候调度器无法解决 CPU 限制问题?
当工作是一次不可分割的计算时——一次大型密码学运算、一个无法分块的内存内转换,或者任何必须单趟完成的事情。拆分只对可分割的工作负载有帮助,比如批量导入、群发邮件和同步任务。对于不可分割的重计算,请使用 Cloudflare Queues、Durable Objects,或者把这一步移到一个为长任务而建的运行时上。
如果你也在研究云计算和运维,欢迎参考本站的 IT 教程文章,一起把技术学扎实。