前面了解 Cloudflare 的一些能力时,看到 Workers 总觉得它像一台部署在 Cloudflare 上的 Node.js 服务器。真正动手创建一个 Worker 后,才发现它和传统服务并不是一回事。
Worker 不是一台服务器,也不是一个需要自己常驻运行的进程。它更像是部署在 Cloudflare 网络中的一段事件处理代码:请求来了,Cloudflare 调用它;它处理完请求,返回结果。
最常见的事件就是 HTTP 请求。一个最小的 Worker 大致是这样:
export default {
async fetch(request: Request): Promise<Response> {
return new Response("Hello Worker!");
},
};
这里没有 app.listen(),也没有端口号。我们只需要实现 fetch,请求到来时 Cloudflare 会把请求交给它,并把返回的 Response 发回给用户。
如何开始使用
Cloudflare 提供了 Wrangler 作为本地开发和部署工具。创建项目、在本地运行、部署到线上,通常只需要下面几步:
npm create cloudflare@latest -- hello-worker
cd hello-worker
npm run dev
npm run dev 会在本地启动开发环境。修改 src/index.ts 后,访问本地地址就可以看到结果。确认没有问题后,再执行:
npm run deploy
Wrangler 会将项目中实际需要的代码和配置部署到 Cloudflare。部署完成后,Worker 可以绑定到 workers.dev 地址、自定义域名,或者站点的某个路由。
一个请求是怎样被处理的
用户访问 Worker 的地址时,请求会先到达 Cloudflare 的网络。Cloudflare 根据域名和路由找到对应的 Worker,再调用它的 fetch 方法。
在 fetch 中,可以直接读取 URL、请求头和请求体,也可以用 fetch 请求其他服务。例如可以在这里做鉴权、改写请求、读取数据,或者把请求转发到已有的后端服务。最后返回一个 Response,这一次请求就结束了。
所以 Worker 很适合放在请求入口处。它可以是一个简单的 API,也可以做页面渲染、接口聚合、鉴权、缓存控制,或者在请求到源站之前先处理一遍。
除了 HTTP 请求,Worker 也可以处理定时任务、队列消息等事件。不过理解 HTTP 请求的处理方式后,其他事件的思路也是一样的:Cloudflare 在事件发生时调用代码,而不是让我们维护一个一直等待事件的服务进程。
Serverless 到底是什么
Serverless 并不是没有服务器。服务器依然存在,只是由 Cloudflare 负责运行、扩容、维护和调度。
传统部署时,我们通常需要关心一台机器或一个容器:进程是否启动、监听哪个端口、流量增加时开多少实例、实例异常后如何重启。Worker 把这些事情收进了平台内部。开发者只关注收到什么事件,以及应该返回什么结果。
这也是 Serverless 最重要的变化:从“维护一个长期运行的应用”,变成“描述一次事件该如何处理”。
Cloudflare Workers 使用 V8 isolate 运行代码。它比启动一个完整的 Node.js 进程轻得多,因此可以更快地创建和复用运行环境。但这不表示某个 Worker 会一直留在内存中。下一次请求可能复用原来的 isolate,也可能被分配到新的 isolate。
因此不能把 Worker 的内存当成数据库。例如下面这样的全局变量,不能用于保存用户数据或业务状态:
let count = 0;
它可能因为运行环境被复用而暂时保留,也可能在下一次请求时消失。全局变量可以用作临时优化,但不能作为正确性所依赖的数据来源。
计算和状态分开
这就是 Serverless 开发中很重要的一点:把计算和状态分开。
Worker 负责处理请求和执行业务逻辑;需要持久化的数据,则放到专门的存储中。比如 D1 适合关系型数据,KV 适合配置和读取频繁的键值数据,R2 用来放文件和对象。如果业务需要对某个对象进行有状态的协调,还可以使用 Durable Objects。
这样做一开始会觉得比把数据放在内存里麻烦,但它让服务可以随时扩容、迁移和恢复,而不依赖某个特定进程一直活着。
理解 Worker 的关键不在于它用了什么打包工具,而在于转换这个思路:它不是一台缩小版的服务器,而是一段由平台按事件调用的代码。把状态交给合适的存储,把请求处理交给 Worker,才是使用 Serverless 的正确方式。