巧用DO绕过Cloudflare Worker免费计划的10ms CPU时间限制
前言
一句话总结:将重计算从Worker卸载到DO中。
我见网络上有零星的讨论提到可以将CPU任务卸载到DO中,但没找到一篇详细介绍的教程(也可能是我太火星了),官方文档里也没有类似的建议,能查到的主流用法都是维持ws长连接。我又问了一圈AI,也没找到什么有价值的线索,它们给出的回答也不一致(甚至前后不一致),故来水一帖。
为什么 Duration Object 可以缓解CPU时间限制
什么是 Duration Object
根据官方文档 What are Durable Objects?,原文如下:
A Durable Object is a special kind of Cloudflare Worker which uniquely combines compute with storage. Like a Worker, a Durable Object is automatically provisioned geographically close to where it is first requested, starts up quickly when needed, and shuts down when idle. You can have millions of them around the world. However, unlike regular Workers:
- Each Durable Object has a globally-unique name, which allows you to send requests to a specific object from anywhere in the world. Thus, a Durable Object can be used to coordinate between multiple clients who need to work together.
- Each Durable Object has some durable storage attached. Since this storage lives together with the object, it is strongly consistent yet fast to access.
Therefore, Durable Objects enable stateful serverless applications.
我的英语水平太过于塑料,就不翻译了,简单画一下重点:
- Worker能跑的代码DO基本也能跑
- DO是根据名称(id)来区分实例的,可以单例串行(多Worker共享)也可以多个实例并行
- DO是直接有存储、有状态的
DO是有状态的这一核心特征我们用不上,我们接着往下看文档。
计费与限制
|Duration Object 的计费主要围绕计算和存储展开,计算部分的额度与计费规则如下:|||
| Free plan | Paid plan | |
|---|---|---|
| Requests | 100,000 / day | 1 million, + $0.15/million |
| Includes HTTP requests, RPC sessions, WebSocket messages, and alarm invocations | ||
| Duration | 13,000 GB-s / day | 400,000 GB-s, + $12.50/million GB-s |
其中有两条脚注需要特别注意:
4 Duration is billed in wall-clock time as long as the Object is active, but is shared across all requests active on an Object at once. Calling
accept() on a WebSocket in an Object will incur duration charges for the entire time the WebSocket is connected. It is recommended to use the WebSocket Hibernation API to avoid incurring duration charges once all event handlers finish running. For a complete explanation, refer to When does a Durable Object incur duration charges?.5 Duration billing charges for the 128 MB of memory your Durable Object is allocated, regardless of actual usage. If your account creates many instances of a single Durable Object class, Durable Objects may run in the same isolate on the same physical machine and share the 128 MB of memory. These Durable Objects are still billed as if they are allocated a full 128 MB of memory.
|这也就是DO是按照挂钟时间×128MB内存来计费的,貌似没提CPU时间的事啊,不急,我们再来看看Limits怎么说:||
| Feature | Limit |
|---|---|
| Number of Objects | Unlimited (within an account or of a given class) |
| Maximum Durable Object classes | 500 (Workers Paid) / 100 (Free) |
| Storage per account | Unlimited (Workers Paid) / 5GB (Free) |
| Storage per class | Unlimited |
| Storage per Durable Object | 10 GB |
| Key size | Key and value combined cannot exceed 2 MB |
| Value size | Key and value combined cannot exceed 2 MB |
| WebSocket message size | 32 MiB (only for received messages) |
| CPU per request | 30 seconds (default) / configurable to 5 minutes of active CPU time |
这里的active CPU time就是指的CPU时间,居然没有单独限制免费计划?!
|我们再来跟Worker的限制对比一下:|||
| Feature | Workers Free | Workers Paid |
|---|---|---|
| Request | 100,000 requests/day 1000 requests/min | No limit |
| Worker memory | 128 MB | 128 MB |
| CPU time | 10 ms | 5 min HTTP request 15 min Cron Trigger |
| Duration | No limit | No limit for Workers. 15 min duration limit for Cron Triggers, Durable Object Alarms and Queue Consumers |
看起来,虽然Worker对免费计划有单独的10ms限制,但DO却没有,那理论上所有请求都用DO处理请求的话,平均CPU时间可以到1.04s?这…这对吗?
对不对一试便知
剧透一下,对的,从这里开始就是教程了
从一个最简单的Demo开始
我们来实现一个最简单的处理HTTP请求的DO试试。
定义 Durable Object 类
1 | // src/index.ts |
在 wrangler.toml 中绑定
1 | name = "do-cpu-test" |
定义 Env 类型
1 | interface Env { |
从 Worker 调用 DO
1 | export default { |
串行复用与并行处理
还记得之前说过DO是通过名称(id)来识别实例的么?我们可以通过传递id来选择是否创建新的实例。刚才的代码中名称固定是"singleton",这样每次获取的都是同一个实例,一次只能处理一个请求,这在高并发下会排队,不过优势是可在DO内部维护状态(本文用不到)。
如果想并行处理:
每次请求都创建一个新的实例
可以直接使用newUniqueId()创建新id。
1 | export default { |
按需区分(如请求参数、用户id)
1 | export default { |
完整的示例
这里我实现了两个端点,分别在Worker和DO中执行PBKDF2迭代,来模拟实际CPU密集任务。
用法如下:
1 | # Worker 内执行(很快会撞 CPU 限制) |
wrangler.toml
1 | name = "do-cpu-test" |
src/index.ts
1 | /// <reference types="@cloudflare/workers-types" /> |
package.json
1 | { |
测试结果
我直接把reps拉到1000,除了玄学1101外没遇到别的限制。

这个CPU时间是free plan的Worker跑到冒烟都跑不出来的。
结论
现在可以回答开头的问题了:这对,DO真的是30s CPU时间,只是DO还可能会受一些别的限制,比如释放不及时、同一台物理机上的DO会共用128M内存、创建DO和往返需要额外挂钟时间等。
现在我们来通俗的总结一下免费计划中Worker和DO的区别,这就像是占着茅
还是文雅的总结一下好了,这就像是在一家会员制餐厅里:
- Worker是点餐的:点了多少才能吃多少(CPU时间限制),但是这个座位想占多久占多久(挂钟时间不限制)
- Durable Object是吃自助餐的:按座位数×时间算钱,但占着座位的时候可以一直猛猛吃。
无脑DO不可取,但适当利用可以极大拓展免费版Worker的可能,比如可以采用自实现的高迭代次数的密码hash、解析大体积JSON……
不知各位在用Worker构建项目时是否也遇到了CPU时间限制的困扰呢?也许DO就是你项目的最后一块拼图。