推出 Workers Cache:增强 Cloudflare Workers 的缓存能力

发布于

今天,我们推出了 **Workers Cache**:一个位于 Worker 前面的 [分层缓存](https://developers.cloudflare.com/cache/how-to/tiered-cache/),只需一行 Wrangler 配置和您熟悉的 `Cache-Control` 头即可完成配置。

当启用 Workers Cache 时,所有可缓存请求首先会访问 Cloudflare 的缓存。如果缓存中有新鲜的响应,Cloudflare 会直接返回,而您的 Worker 不会运行,这样您就不需要支付 CPU 时间。如果缓存未命中,您的 Worker 会运行;如果响应是可缓存的,Cloudflare 会为下一个请求存储它。接下来任何来自地球上任何地方的请求都可以直接从缓存中提供。

整体配置仅需一块配置:
```json
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2026-05-01",
"cache": {
"enabled": true
}
}
```

之后,您可以通过设置响应的 HTTP 头来控制缓存:
```javascript
return new Response(body, {
headers: {
"Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
"Cache-Tag": "products,product:123",
},
});
```
当内容发生变化时,您的 Worker 会清除自己的缓存:
```javascript
await ctx.cache.purge({ tags: ["product:123"] });
```

这是整个 API。没有区域需要配置,没有规则引擎需要设置,没有单独的缓存需要提供,也不需要登录到第二个产品。Worker 的代码就是配置表面,缓存跟随 Worker 在任何地方运行 —— 在自定义域名上、在 `workers.dev`、后面有服务绑定、在预览中、或是在 [Workers for Platforms](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/) 的租户中运行。一个 Worker,一个缓存,配置一次。

这是表面层。下面还有很多东西:在我们整个网络上的分层缓存,完全支持 [stale-while-revalidate](https://developers.cloudflare.com/changelog/post/2026-02-26-async-stale-while-revalidate/),使陈旧的响应不会阻塞用户,通过 [Vary](https://developers.cloudflare.com/workers/cache/#content-negotiation-with-vary) 进行内容协商,以及通过 [ctx.props](https://developers.cloudflare.com/workers/runtime-apis/context/#props) 实现多租户安全的缓存键,按标签或路径前缀进行程序化清除,而我们认为最大解锁的部分是,缓存位于每个 Worker 的入口点前,而不仅仅是公共入口,并可以对哪些缓存以及哪些不缓存进行每个入口点控制。这意味着您可以将缓存直接构建到应用程序的结构中:多个入口点的链,按需插入缓存阶段,通过两侧的代码进行配置。接下来我们将详细介绍这些内容。

Workers Cache 今天就在所有 Worker 的任何计划中可用,在 Wrangler 中启用。

这是我们一直希望 Worker 拥有的缓存 API。下面我们将讨论为什么整合此功能花了这么长时间,因其带来的可能性,以及未来的计划。

## 为什么服务器渲染的应用需要前置缓存

当我们 [在2017年推出 Workers](https://blog.cloudflare.com/introducing-cloudflare-workers/) 时,主张是您可以在 Cloudflare 的网络上运行代码,以在请求到达源之前进行转化。Worker 位于缓存和源的 **前面**:

这对于我们针对的用例是正确的模型。如果您想在每个请求中添加头部、重写 URL、进行 A/B 测试,或在请求到达源之前过滤流量,将 Worker 放在缓存和源的前面让您对缓存的内容有了完全的控制。客户因此构建了许多惊人的应用。

但世界发生了变化。Workers 不再是一个附加到源的东西,而是成为了 **源**。像 [Astro](https://developers.cloudflare.com/workers/framework-guides/web-apps/astro/)、[TanStack Start](https://developers.cloudflare.com/workers/framework-guides/web-apps/tanstack-start/)、[Next.js](https://developers.cloudflare.com/workers/framework-guides/web-apps/nextjs/)、[Remix](https://developers.cloudflare.com/workers/framework-guides/web-apps/remix/) 和 [SvelteKit](https://developers.cloudflare.com/workers/framework-guides/web-apps/svelte/) 这样的框架都提供了一个构建您应用的 Cloudflare 适配器。它们没有背景源,Worker **就是**服务器。

当 Worker 是源时,原始架构没有缓存的内容。每个请求都会运行您的代码,即使响应与您几秒钟前返回的响应完全相同。尽管 [Workers 运行时足够快](https://x.com/KentonVarda/status/1783996343813652801),能够毫无压力地处理数千万个请求,但“足够快的响应每个请求”仍会在每个页面加载上增加延迟和每次调用的 CPU 时间。而在服务器渲染的应用中,每个页面加载本质上都是一次渲染。

Workers Cache 改变了架构。Cloudflare 的缓存现在位于 Worker 的前面:

如果缓存命中,您的 Worker 根本不会运行。Cloudflare 返回缓存的响应,您的 CPU 计费保持为零。如果未命中,您的 Worker 运行一次,填充缓存,然后下一个请求 — 无论来自何处 — 都可以从缓存中提供,而无需调用您的代码。

这是之前在 Workers 上进行服务器端渲染所缺失的。以往,您必须在两个不理想的选项中选择:

1. **在构建时预渲染所有内容**(“静态站点生成”)。页面加载快速,但每次更改都需要完全重建和重新部署。对于具有几千页的文档站点,这需要5-10分钟。对于大型电商网站来说,情况更糟 —— 每次触动都要跑一次构建。
2. **每次请求都渲染每个页面**。内容始终保持最新,但每次页面加载都需支付渲染成本,每个访客都需承担延迟。

Workers Cache 为您提供了第三个选项:按需在服务器上渲染,缓存渲染的响应,并在您选择的生存时间(TTL)上刷新。首次请求新页面仍会渲染。每个后续请求,直到缓存过期,都将以静态页面的方式提供。当缓存过期时,下一请求将触发重新渲染 —— 而通过 `stale-while-revalidate`,即使那个请求也不会等待。

您获得了静态站点的速度,而无需构建时间,拥有服务器渲染的新鲜度而无需成本。没有框架特定的机制,例如增量静态再生。只是 HTTP 缓存,按其设计的方式工作,位于被设计为源的代码前面。

## `stale-while-revalidate` 让反应显得瞬时

[`stale-while-revalidate`](https://datatracker.ietf.org/doc/html/rfc5861#section-3) 指令告诉 Cloudflare,当缓存响应过期时,允许其立即提供陈旧的副本 **同时在后台刷新响应**。Cloudflare 早期 [全面支持 `stale-while-revalidate`](https://developers.cloudflare.com/changelog/post/2026-02-26-async-stale-while-revalidate/) 指令,使得“我们缓存您的 Worker”变成了“您 Worker's 网站感觉静态”。

如果没有此指令,缓存条目过期后的首次请求必须等待 Worker 从头渲染页面。用户会看到延迟。有了这个指令,缓存条目过期后的首次请求立即获取陈旧页面(并带有 `Cf-Cache-Status: UPDATING` 头部),Worker 会在后台运行以补充缓存。包括触发刷新的人在内的每个用户都会得到缓存速度的响应。

在实践中,这看起来像这样:
```javascript
export default {
async fetch(request) {
const html = await renderPage(request);
return new Response(html, {
headers: {
"Content-Type": "text/html; charset=utf-8",
// Treat as fresh for 5 minutes; serve stale for up to an hour
// while a background refresh runs.
"Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
},
});
},
};
```

让我们来看看形成以上理论的心理模型:
- **新鲜窗口** (`max-age`):Cloudflare 提供缓存响应。您的 Worker 不会运行。
- **过期窗口** (`stale-while-revalidate`):Cloudflare 提供缓存响应。您的 Worker 在后台运行以刷新。没有用户等待。
- **超出两者窗口之外**:Cloudflare 运行您的 Worker 以生成新的响应,用户则要等待刷新作业。

您可以选择自己配置的窗口。对于每几分钟更新一次的产品目录,`max-age=300`,`stale-while-revalidate=3600` 意味着访客几乎从不等待,而您的 Worker 仍然运行足够的次数以保持内容新鲜。对于几乎不变更的博客存档,`max-age=86400`,`stale-while-revalidate=2592000` 则意味着您的 Worker 每天仅运行一次。

对于新页面的首次请求是唯一一项需要支付完整渲染成本的请求,之后,该页面对访客的表现就像静态输出,而您的 Worker 仍然掌控页面的生成方式。

## 一个 URL,多种表现:`Vary` 有效

真实应用程序很少会将相同的内容字节返回给每个客户。同一个产品页面可能对浏览器返回 HTML,对 API 客户端返回 JSON。同一个图像可能对支持 WebP 的客户端返回 WebP,且对不支持的客户端返回 JPEG。同一个主页可能根据用户返回英语、法语或日语内容。

在没有缓存的情况下,这很简单 —— 你的 Worker 读取请求头并返回相应内容。而在有缓存的情况下,这往往会变得复杂。大多数缓存提供两个糟糕的选项:要么不缓存有多种表现的 URL,要么缓存一种表现并对所有人提供。

Workers Cache 支持标准 HTTP 的 `Vary` 头,这是解决此问题的正确方法。当您的 Worker 返回带有 `Vary: Accept-Encoding`(或 `Accept`、`Accept-Language` 或其他任何请求头)的响应时,Cloudflare 会 per 不同的头部组合存储独立的缓存变体 —— 仅返回与输入请求匹配的存储值的变体。

```javascript
export default {
async fetch(request) {
const accept = request.headers.get("Accept") ?? "";
const wantsWebp = accept.includes("image/webp");

const body = wantsWebp ? await fetchWebpImage() : await fetchJpegImage();

return new Response(body, {
headers: {
"Content-Type": wantsWebp ? "image/webp" : "image/jpeg",
"Cache-Control": "public, max-age=3600",
// 每个不同的 Accept 头部值缓存一个独立的变体。
Vary: "Accept",
},
});
},
};
```

一个 URL,两个缓存变体。发送 `Accept: image/webp,*/*` 的浏览器获取 WebP,发送 `Accept: image/jpeg` 的浏览器获取 JPEG。两者都来自缓存。在首次请求时,您的 Worker 将存储这两个变体,之后再调用时都不需要运行。

这是处理内容协商的常规 HTTP 标准,Workers Cache 按照 [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-vary) 和 [RFC 9111](https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-cache-keys-with) 的描述进行实现。没有允许列表限制您可以在什么头部上使用 `Vary`。您可以列出所需的任何内容,Cloudflare 将基于原始值对变体进行键入。文档详细介绍了 [如何在边缘 Worker 中控制变体扩展的问题](https://developers.cloudflare.com/workers/cache/configuration/#vary) —— 如何将变体限制在合理范围内,为什么清除缓存失效所有变体,以及一个使用 `Vary: *` 的例外情况,它会完全禁用缓存。

## 这是您 Worker 的缓存,而不是您区域的

在我们讨论这一切可能带来的潜能之前,有一个值得提到的概念转变。

Cloudflare 一直以来都有 cache。它在区域级别进行配置:缓存规则、页面规则、缓存文件扩展名列表、缓存保留、分层缓存拓扑、自定义缓存键。所有这些都是按区域设置的,历史上 Worker 必须适应该区域的配置或予以规避。

Workers Cache 则有所不同。它是 **您 Worker 的缓存** —— 它属于 Worker,而不是属于某个地区。这有几个重要影响:
- **没有区域配置需要管理。** 缓存规则、缓存级别设置、文件扩展名列表、页面规则 —— 这些都不适用于 Workers Cache。Worker 的 `Cache-Control` 头部即为配置。
- **缓存随 Worker 移动,而不是主机名。** 绑定在 `api.example.com`、`api.example.net` 的 Worker,无论通过哪种方式调用,都共享一个缓存。对 `/users/42` 的请求,无论它是通过哪条路径进来的,都会命中同一缓存条目。
- **缓存在** `workers.dev` **中工作。** 它在 [预览 URL](https://developers.cloudflare.com/workers/configuration/previews/) 中工作(每个预览都有自己的缓存,因此测试更改不会影响生产)。它在 [Workers for Platforms](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/) 中工作(每个用户 Worker 都有自己的缓存,与调度程序和其他租户隔离)。所有这些以前在缓存中是次要元素,但现在不再是。
- **清除操作是作用于 Worker 的入口点。** 当您调用 `ctx.cache.purge({ purgeEverything: true })` 时,您只是在清除您 Worker 入口点的缓存。没有风险会清空您区域的其他内容。一个 Worker 的部署不会使另一个的缓存失效。

关于缓存的配置,都在代码中进行配置:哪些路径获取更长的 TTL(根据路径分支并设置不同的 `max-age`),哪些请求跳过缓存(返回 `Cache-Control: private`),缓存键的形状(控制哪些内容进入 `ctx.props`,在调度前在网关 Worker 中规范化 URL)。您已经编写的 Worker 是配置的表面。

完整的文档深入探讨了这个内容,您可以查看 [Workers Cache: 您的 Worker 缓存](https://developers.cloudflare.com/workers/cache/)。

## 两个层级,所有 Worker,无需配置

Workers Cache 默认是 **区域性分层的**。有两个层级:
1. **较低层级** 在离用户最近的 Cloudflare 数据中心。每个接收您 Worker 流量的数据中心都有自己的下层缓存。
2. **较高层级** 聚合来自整个网络的内容填充。

请求首先命中下层。如果命中,直接返回响应。若未命中,下层会咨询上层。上层如果命中,返回的响应会存储在下层缓存中。只有当两个层级都未命中时,您的 Worker 才会运行 —— 运行的响应会在两个层级中存储。

其原因在于 **全球首个请求** 填充上层缓存。接下来的每个请求,无论是来自哪个数据中心,都可以直接从上层缓存中提供,而无需运行您的 Worker,甚至即使在该数据中心的下层缓存从未见过该请求。缓存命中率显著高于单个平面缓存层中的情形,这正是您的 Worker 是源时所需的。

这是实现 [分层缓存](https://developers.cloudflare.com/cache/how-to/tiered-cache/) 的相同拓扑,每个启用缓存的 Worker 默认获得分层缓存的功能。若您的 Worker 使用了 [智能位置](https://developers.cloudflare.com/workers/configuration/placement/),缓存与其良好组合:层级缓存首先被咨询,只有在两个层级未命中后,智能定位才会将执行路由至您的源附近。我们将在文档中进一步解释这两层的交互,包括一些待解决的边缘案例。

---

原文链接:[点击查看](https://blog.cloudflare.com/workers-cache/)

评论

暂无评论。