Worker 现在可以在前端拥有其专属缓存

Dan LapidConnor Harwood

阅读时间:19 分钟

本文另有 English繁體中文.

BLOG-3262 hero image

今天我们将推出 Workers Cache:这是一个分层缓存,它位于您的 Worker 前端,只需一行 Wrangler 配置以及您已熟悉的 Cache-Control 标头即可进行配置。

启用 Workers 缓存后,所有发送到您 Worker 的可缓存请求都会首先命中 Cloudflare 的缓存。如果有最新缓存的响应,Cloudflare 会直接将其返回,您的 Worker 不会运行,您也无需为此支付 CPU 时间。如果未命中缓存,您的 Worker 会运行,如果响应可缓存,Cloudflare 会将其存储以备下一个请求使用。来自全球任何位置的下一个请求都可以直接从缓存中获取服务。

BLOG-3262 image1

整个流程是一个配置块:

{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-05-01",
  "cache": {
    "enabled": true
  }
}

之后,您可以通过在响应中设置标头,以 HTTP 一直希望的方式控制缓存:

return new Response(body, {
  headers: {
    "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
    "Cache-Tag": "products,product:123",
  },
});

当内容发生变化时,Worker 会清除自己的缓存:

await ctx.cache.purge({ tags: ["product:123"] });

这就是整个 API。无需配置任何区域,无需设置任何规则引擎,无需提供单独的缓存,也无需登录任何其他产品。Worker 的代码就是配置界面,缓存会跟随 Worker 运行,无论它是在自定义域、workers.dev、在服务绑定后端、预览版还是Workers for Platforms 租户中运行。一个 Worker,一个缓存,一次配置。

这只是表面层。底层技术非常强大:覆盖整个网络的分层缓存、完全支持 stale-while-revalidate 以确保过期响应不会阻塞用户,通过 Vary 进行内容协商,通过 ctx.props 接收多租户安全缓存键,按标签或路径前缀进行程序化清除;以及我们认为最关键的一点,在每个 Worker 入口点前端设置缓存,不仅仅是公共入口点,并且可以针对每个入口点单独控制缓存哪些入口点,不缓存哪些入口点。最后一点意味着您可以将缓存直接集成到应用架构:这是一系列入口点,缓存阶段可以插入您所需的任何位置,由入口点两端的代码进行配置。我们将在下文详细介绍相关信息。

Workers 缓存现已面向任何 Cloudflare 计划中的所有 Worker 开放,并可通过 Wrangler 使用。

这是我们一直以来希望 Workers 拥有的缓存 API。下文将阐述我们为什么花了这么长时间才推出此功能,它带来了哪些可能性,以及未来的发展方向。

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

我们在 2017 年推出了 Workers,当时的营销用语是:您可以在 Cloudflare 网络上运行代码,在请求到达源服务器之前对其进行转换。Worker 位于缓存与源服务器的前端

BLOG-3262 image5

这正是我们当时所针对的使用场景的理想模型。如果您想在每个请求中添加标头、重写 URL、进行 A/B 测试,或在流量到达源服务器之前过滤流量,则将 Worker 置于缓存与源服务器的前端,您就可以完全控制缓存哪些内容,不缓存哪些内容。客户利用它构建了许多令人惊叹的应用。

但局面很快发生变化。Workers 逐渐不再是附加在源服务器上的组件,而是成为了本身。AstroTanStack StartNext.jsRemixSvelteKit 等框架提供了 Cloudflare 适配器,可以将你的应用构建为 Worker。它们背后没有源服务器。Worker 就是服务器。

Worker 成为源服务器之后,原始架构没有任何内容可以缓存。每个请求都会执行代码,即使响应与您一秒之前返回的响应每个字节完全相同。Workers 运行时速度快得足以处理每个请求(我们经常运行每秒处理数千万个请求的服务器,而且毫不费力),但“快得足以处理每个请求”仍然会在每次页面加载时带来延迟,并在每次调用时增加 CPU 时间。因为在服务器端渲染的应用中,根据定义,每次页面加载都是一次渲染。

Workers 缓存颠覆了这种架构。Cloudflare 的缓存现在位于 Worker 的前端:

BLOG-3262 image9

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

这是 Workers 服务器端渲染缺少的内容。过去,您不得不在两个不尽如人意的选项中做出选择:

  • 在构建时预渲染所有内容(“静态网站生成”)。页面加载速度快,但每次更改都需要完全重建和重新部署。对于一个拥有几千页的文档网站,这需要 5-10 分钟。对于大型电子商务网站来说,情况更糟,每次执行任何操作时,构建版本都会运行。
  • 在每次请求时渲染每个页面。内容实时更新,但每次页面加载都会产生渲染成本,每个访客都遭受延迟。

Workers 缓存为您提供第三种选择:按需服务器端渲染,缓存已渲染的响应,并根据您选择的生存时间 (TTL) 刷新。首次请求新页面时,页面仍会渲染。在缓存过期之前,后续的每个请求都会像静态页面一样渲染。如果缓存过期,下一个请求会触发重新渲染,而使用 stale-while-revalidate 指令,即使是重新渲染的请求也不会等待。

您可以获得静态网站的速度,但不会耗费构建时间;以及获得服务器端渲染的新鲜度,而无需承担额外的成本。没有类似增量静态再生那样的框架专用机制。仅使用 HTTP 缓存,按照其设计初衷运行,位于原本设计为源服务器的代码前端。

stale-while-revalidate 指令让人感觉响应速度感觉非常快

stale-while-revalidate 指令告诉 Cloudflare,当缓存的响应过期时,它可以立即提供过期的页面副本并在后台刷新响应。Cloudflare 今年早些时候推出了对 stale-while-revalidate 的全面支持,这个指令将“我们缓存您的 Worker”变成“您的 Worker 网站带来如同静态页面的体验。”

如果没有该指令,缓存条目过期后的第一个请求必须等待 Worker 从头开始渲染页面。用户会感受到这种延迟。因此,过期后的第一个请求会立即获得过期的页面(带有 Cf-Cache-Status: UPDATING 标头),而 Worker 则在后台运行以重新填充缓存。每个用户,包括触发刷新操作的用户,都会获得缓存速度的响应。

BLOG-3262 image3

在实践中,这看起来是这样的:

 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 格式,对于不支持 WebP 的客户端则可能是 JPEG 格式。同一主页可能会以英语、法语或日语返回,具体取决于用户。

不使用缓存很容易实现这一点,Worker 只需读取请求标头并返回正确的内容。而使用缓存通常会出现问题。大多数缓存提供两个糟糕的选择:要么不缓存具有多种表现形式的 URL,要么缓存一种表现形式并将其提供给所有用户。

Workers 缓存支持标准的 HTTP Vary 标头,这是解决该问题的正确方法。当 Worker 返回的响应包含 Vary: Accept-Encoding(或者 Accept,或 Accept-Language 或任何其他请求标头)时,Cloudflare 会根据这些标头的不同组合存储一个单独的缓存变体,并且仅返回存储值与传入请求匹配的变体。

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",
        // Cache a separate variant per distinct Accept header value.
        Vary: "Accept",
      },
    });
  },
};

一个 URL,两种缓存变体。发送 Accept: image/webp,*/* 的浏览器会获得 WebP 格式。发送 Accept: image/jpeg 的浏览器会获得 JPEG 格式。两者都来自缓存。Worker 会在首次请求时写入两种变体,之后这两种变体不再运行。

这是常用的 HTTP 内容协商标准,Workers 缓存按照 RFC 9110RFC 9111 所述的方式实现它。没有允许列表来定义哪些标头可以执行 Vary 操作。您列出所需的标头,Cloudflare 会根据这些标头的逐字值生成不同的变体。文档涵盖了各种特殊情况:如何在网关 Worker 中通过规范化标头来控制变体的传播,为什么清除操作会使 URL 的所有变体一起失效,以及唯一一种会完全禁用缓存的情况 (Vary: *)。

这是 Worker 缓存,不是区域缓存

在探讨这一切带来哪些可能实现的变化之前,有一个值得注意的命名概念转变。

Cloudflare 一直以来都有缓存。它是在区域级别配置:缓存规则、Page Rules、缓存文件扩展名列表、Cache Reserve、分层缓存拓扑、自定义缓存键。所有这些都是根据每个区域设置,过去,Worker 不得不适应该区域的配置或找到变通方法。

Workers 缓存则不同。这是 Worker 缓存,它属于 Worker,而不是某个区域。这会产生一系列重要影响:

  • 没有要管理的区域配置。缓存规则、缓存级别设置、文件扩展名列表、Page Rules 都不适用于 Workers 缓存。Worker 的 Cache-Control 标头就是配置。
  • 缓存跟随 Worker,而不是主机名。绑定到 api.example.comapi.example.net 以及通过服务绑定调用的 Worker 共享同一个缓存。无论请求来自哪个源,对 /users/42 的请求都会命中同一个缓存条目。
  • 缓存适用于 workers.dev它适用预览版 URL(每个预览版有各自的缓存,因此,测试更改不会影响生产环境)。它适用 Workers for Platforms(每个用户 Worker 有各自的缓存,与调度程序和其他租户隔离)。所有这些曾经在缓存中都处于次要地位。现在情况已经不同了。
  • 清除操作范围限定在 Worker 的入口点。如果调用 ctx.cache.purge({ purgeEverything: true }),则仅清除 Worker 入口点的缓存。不存在清空区域的其他内容的风险。不存在部署一个 Worker 导致另一个 Worker 数据失效的风险。

缓存相关的配置都在代码中完成:哪些路径需要更长的 TTL(根据路径进行分支并设置不同的 max-age 值)、哪些请求会绕过缓存(返回 Cache-Control: private)、缓存键如何构建(控制哪些内容进入 ctx.props,在网关 Worker 分发请求之前规范化 URL)。您已经编写的 Worker 就是配置界面。

完整文档详细介绍了这一点,请参阅 Workers 缓存:您的 Worker 缓存

两层结构,每个 Worker 各司其职,无需配置

Workers 缓存默认按区域分层。共有两层:

  • 底层缓存位于距离用户最近的 Cloudflare 数据中心。每个接收 Worker 流量的数据中心都有其各自的底层缓存。
  • 上层缓存聚合整个网络的填充缓存。上层缓存数量较少,当未命中缓存时,每个底层缓存会询问上层缓存。

请求首先到达底层缓存。如果命中,则提供响应,请求结束。如果未命中,则底层缓存会询问上层缓存。如果上层缓存命中,则返回响应,并在返回过程中将其存储在底层缓存中。只有当底层缓存和上层缓存都未命中时,Worker 才会实际运行,并且该运行生成的响应会同时存储在这两层缓存中。

BLOG-3262 image2

这一点至关重要,因为全球任何地方的第一个请求都会填充上层缓存。后续来自任何数据中心的请求均可直接从上层缓存处理,无需运行 Worker,即使该数据中心的底层缓存之前从未处理过该请求。缓存命中率远高于单一扁平缓存层,这正是 Worker 作为源服务器时所需的功能。

这与目前区域分层缓存的拓扑结构相同,只是您无需进行任何配置。没有“为我的 Worker 服务器启用分层缓存”的对话框。每个已启用缓存的 Worker 都会免费获得分层缓存功能。

如果 Worker 使用 Smart Placement,则缓存可与之顺利配合:首先查询所有层级的缓存,只有在二者都未命中的情况下,Smart Placement 才会将执行路由到靠近源服务器的位置。关于这些层如何交互,包括我们计划改进的一些不足之处,将在文档中进行更详细的说明。

靠近用户靠近数据运行应用

Web 性能领域一直存在一个尚未完全解决的对立难题:您希望代码靠近用户运行(因为用户与服务器之间的往返在关键路径中),您也希望代码靠近数据运行(因为每个数据库查询也都是一次往返)。但是,选择其中之一,另一个就会变慢。

多年来,我们一直在努力兼顾两者。Cloudflare 网络将我们与全球约 95% 的互联网用户之间的延迟控制在 50 毫秒以内Smart PlacementPlacement Hints 让您可以将代码始终置于数据附近,无需考虑云区域。但直到现在,这两者仍然无法完美结合。您可以选择“靠近用户”或“靠近数据”,如果您希望自己的应用兼顾二者,则必须成为 Cloudflare 专家。我们知道,我们可以做得更好。

Workers 缓存正是弥合这一差距的关键。由于缓存属于 Worker 而不是区域,并且由于服务绑定以及 Workers 之间的 ctx.exports 调用通过缓存进行,因此,您可以将应用构建成一个 Worker 链(每个 Worker 都在其应该运行的位置运行),缓存是这些 Worker 的连接点。

架构如下:

BLOG-3262 image8
  • Worker A 在靠近用户的位置运行。它处理每个请求中那些廉价的、延迟敏感环节:身份验证、速率限制、路由、标头规范化,渲染不依赖数据的 HTML 页面外壳
  • Worker B 由于 Smart Placement 或显式 Placement Hint 的帮助,在靠近数据的位置运行。它负责处理繁重的工作:服务器端渲染获取数据的页面、读取产品目录、生成搜索结果、聚合 API 以及执行昂贵的转换。
  • Workers 缓存位于 Worker B 的前端。当 Worker A 通过服务绑定调用 Worker B 时,Cloudflare 首先检查 Worker B 的缓存。如果命中,Worker A 收到响应,而 Worker B 完全不会运行,这种情况下无需数据中心跳转、无需数据库查询、无需渲染工作。

缓存命中路径变为:用户 → 靠近用户的 Worker A → Worker B 的缓存命中 → 响应。只有在缓存未命中时,才会为数据跳转付费。您的热门页面以代码直接面向用户的速度运行,而冷门页面在执行时也能受益于靠近数据的运行。

无需进行任何特殊的架构设计,即可实现这一点。只需将您的应用编写成两个 Worker,通过服务绑定将其中一个 Worker 指向另一个 Worker,在 Worker B 的 wrangler.jsonc 文件中启用缓存,这样就完成了。

BLOG-3262 image7

默认支持多租户,并带有 ctx.props 属性

如果您要缓存返回用户特定数据的 Worker,例如,为每个登录用户提供不同内容的 API,则需要一种方法来确保一个用户绝不会看到另一个用户的缓存响应。标准解决方案是“不缓存已经过身份验证的请求”,Cloudflare 的自动绕过 Authorization 标头机制正是如此。但是,“不缓存任何内容”会完全失去性能优势。

Workers Cache 通过将调用方的 ctx.props 属性作为缓存键的一部分,解决了这个问题。当一个 Worker 通过服务绑定调用另一个 Worker,并传递 ctx.props(包括用户 ID、租户 ID 或任何其他标识符)时,拥有不同 props 属性的调用方会获得单独的缓存条目。一个用户的响应绝不会泄露到另一个用户的缓存中。

import { WorkerEntrypoint } from "cloudflare:workers";

interface Props { userId: string; }

export default class Backend extends WorkerEntrypoint<Env, Props> {
  async fetch(request: Request): Promise<Response> {
    // ctx.props.userId is part of the cache key. User A and User B
    // requesting the same URL get separate cached entries.
    const { userId } = this.ctx.props;
    const data = await loadUserData(userId);

    return new Response(JSON.stringify(data), {
      headers: {
        "Content-Type": "application/json",
        "Cache-Control": "public, max-age=300",
      },
    });
  }
}

典型模式是在网关 Worker 中对请求进行身份验证,移除 Authorization 标头,将已验证用户的 ID 设置到 ctx.props 中,然后调用缓存的后端 Worker。网关在每个请求上运行(为了进行身份验证,必须这样做),但昂贵的后端只有在尚未为该用户创建缓存条目时才运行。经过身份验证的 API 从“不可缓存”变为“已按用户缓存且完全安全”,缓存键负责执行隔离。文档在“使用 ctx.props 实现多租户安全性”以及“按用户已进行身份验证的响应”示例中对此进行了详细介绍。

其他 CDN 要求您在正确性与命中率之间做出选择:要么使用每个用户的令牌来缓存数据,要么将每个请求发送回源服务器获取授权。Workers 缓存让您可以在边缘共享已缓存的 API 响应,同时保留每个请求的授权边界。我们目前尚未发现其他 CDN 为经过身份验证的多租户 API 提供这样的内置模型。我们为 Cloudflare 做到这一点感到非常自豪。

每个 Worker 入口点之间的缓存

这是我们认为 Workers 缓存中最重要的突破,如果您将其理解为“恰好在 Worker 前端运行的 CDN 缓存”,那么这部分可能最难理解。

Workers 缓存位于每个 Worker 入口点前端:默认导出、每个已命名的 WorkerEntrypoint,以及同一 Worker 中入口点之间通过 ctx.exports 进行的每一次调用。正是最后这一点改变了您可以构建的内容。

当一个入口点通过 ctx.exports 调用另一个入口点时,缓存会像评估来自浏览器的请求同样的方式评估该调用。如果命中,则返回缓存的响应,被调用方不会运行。如果未命中,则被调用方运行,并将其响应存储在自己的缓存键下,由被调用方的入口点、路径、查询字符串和 ctx.props 作为键码。调用方仍然会在每一次请求时运行,但其传递给被调用方的任何内容都会独立地存储。

您可以决定每个入口点缓存哪些内容。在 Wrangler 配置中,exports 映射让您可以按名称为每个入口点启用或禁用缓存(“default”表示默认导出)。选择启用即可缓存入口点生成的响应;选择禁用即会使其在每次请求时运行。网关或路由器入口点(任何进行身份验证、标准化或分发的入口点)都应选择禁用,以便始终运行,并且决不会从缓存中提供其自身的输出。

这将为您提供可编排的基础组件。您可以将 Worker 编写成一系列小型入口点:身份验证、标准化、路由、昂贵的读取、数据层,并让 Workers 缓存插入您想要的位置。每个缓存的入口点都是一个记忆单元,拥有自己的键、生存时间以及用于清除缓存的专属标签命名空间。任何您想要配置的缓存信息,例如运行时间、基于的键值、失效时间,都可以使用普通 Worker 代码表示:调用哪个入口点、转发哪个请求、传递哪些 ctx.props 属性,以及设置哪些 Cache-Control 参数。

为了更具体地说明这一点,请看这个 Worker 示例,它完成了在其他平台难以同时实现的三项任务:对每个请求进行身份验证,在多租户安全的缓存键后面缓存昂贵的后端,以及在数据更改后使该缓存失效。

按入口点配置缓存。网关之所以必须在每次请求时运行,既是为了进行身份验证,也是因为已缓存的网关响应会跳过身份验证检查;因此,我们在默认入口点禁用缓存,并且只在内部入口点启用缓存:

{
  "name": "my-worker",
  "main": "src/index.ts",
  "compatibility_date": "2026-05-01",
  "cache": { "enabled": true },
  "exports": {
    // The gateway runs on every request — don't cache it.
    "default": { "type": "worker", "cache": { "enabled": false } },
    // Cache the expensive inner entrypoint.
    "CachedBackend": { "type": "worker", "cache": { "enabled": true } }
  }
}
import { WorkerEntrypoint } from "cloudflare:workers";

interface Env { API_TOKEN: string; }
interface Props { userId: string; }

// Inner entrypoint: the expensive work. Workers Cache sits in front
// of this — on a hit, this code never runs.
export class CachedBackend extends WorkerEntrypoint<Env, Props> {
  async fetch(request: Request): Promise<Response> {
    // ctx.props.userId is part of the cache key, so this is cached
    // separately for every user.
    const { userId } = this.ctx.props;
    const data = await loadExpensiveData(userId);

    return new Response(JSON.stringify(data), {
      headers: {
        "Content-Type": "application/json",
        "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
        "Cache-Tag": `user:${userId}`,
      },
    });
  }

  // Invalidate a user's cached response. purge() is scoped to the
  // entrypoint that calls it, so it must run inside CachedBackend —
  // the entrypoint that owns the cached response.
  async invalidate(userId: string): Promise<void> {
    await this.ctx.cache.purge({ tags: [`user:${userId}`] });
  }
}

// Outer entrypoint: runs on every request to authenticate and route.
// Caching is disabled for it in Wrangler config (above), so it always
// runs and the auth check is never skipped by a cache hit.
export default {
  async fetch(request, env, ctx): Promise<Response> {
    const userId = await authenticate(request, env);
    if (!userId) return new Response("Unauthorized", { status: 401 });

    // Invalidate this user's cache on writes, from the entrypoint that
    // owns it.
    if (request.method === "POST") {
      await handleWrite(request, userId);
      await ctx.exports.CachedBackend.invalidate(userId);
      return new Response("OK");
    }

    // For reads: strip Authorization (otherwise Cloudflare's automatic
    // bypass fires and nothing caches), then dispatch to the cached
    // backend with the authenticated user's identity in ctx.props.
    const forwarded = new Request(request);
    forwarded.headers.delete("Authorization");

    return ctx.exports.CachedBackend.fetch(forwarded, {
      props: { userId },
    });
  },
} satisfies ExportedHandler<Env>;

整套方案只有一个 Worker。一个源文件。一次部署。但有两个执行阶段,也就是在一个小型 exports 块中,关闭网关缓存,启用后端缓存,二者之间有一个缓存,它由用户键控,根据写入路径失效,并在后台刷新期间提供过期数据。缓存阶段并非附加组件。它是程序的一层,用代码编写。

由此组合形成的模式是开放的。同样的特征也适用于:

  • 缓存 Durable Object。将 Durable Object 包装在入口点之后,在响应中设置 Cache-Control,如果命中,则读取操作停止访问 Durable Object。写入操作直接写入 DO 并按标签清除缓存。DO 不会感知到正在进行缓存。
  • Vary 之前标准化 Accept-Encoding。外部入口点从 request.cf.clientAcceptEncoding 恢复原始编码(Cloudflare 前端会对其进行标准化处理以提高缓存效率),然后转发到随实际值而变化的已缓存入口点。命中率居高不下;客户端获得正确的编码。
  • 在缓存之前,移除跟踪参数。外部入口点会标准化 URL 

或者在 ctx.exports 调用中使用 cf.cacheKey 设置自定义缓存键,因此,缓存的内部入口点只能看到标准的形式,而 ?utm_source=anything 会折叠到一个单独的缓存条目。

堆叠这些缓存。单个 Worker 可能拥有一个外部入口点用于身份验证和路由、一个标准化化入口点用于移除跟踪参数并恢复编码标头、一个缓存的入口点位于 Durable Object 前端,以及一个单独的已缓存入口点用于访问未经身份验证的公共 API,每个入口点通过一个缓存阶段连接,您无需配置,只需决定放置位置。文档中的“示例”页面完整演示了几个示例。

我们目前尚未发现其他平台能够做到这一点。CDN 缓存位于源服务器前面。函数平台运行函数。我们目前还没有发现其他平台可以在应用各部分之间提供这样一个缓存,该缓存位于单个可部署单元内部,并且每个缓存阶段由其两侧的代码进行配置。这就是 Workers 缓存的功能。而且,由于它可以与平台提供的其他各项功能(Smart Placement、Durable Objects、服务绑定、ctx.propsctx.exports)结合,因此,您可以构建的模式是开放的。本文只涉及问题的表面,没有深入探究。

提供框架内一流支持

如果使用 Astro 构建应用,Cloudflare 适配器会自动为您连接 Workers 缓存。只需将 cacheCloudflare 提供程序添加到配置中:

// astro.config.mjs
import { defineConfig } from "astro/config";
import cloudflare from "@astrojs/cloudflare";
import { cacheCloudflare } from "@astrojs/cloudflare/cache";

export default defineConfig({
  adapter: cloudflare(),
  output: "server",
  experimental: {
    cache: { provider: cacheCloudflare() },
    routeRules: {
      "/products/*": { maxAge: 300, swr: 3600, tags: ["products"] },
      "/blog/*":     { maxAge: 60,  swr: 86400, tags: ["blog"] },
    },
  },
});

适配器会启用缓存,在 Astro 生成的响应中设置正确的标头,附加用于使缓存失效的 Cache-Tag 值,并提供 cache.invalidate() 辅助函数,用于在内容更改后清除标签。选择服务器端渲染的 Astro 页面会自动进入上述“渲染一次,缓存,后台刷新”流程,无需针对每个路由进行配置,也无需学习特定框架的运行时层。

我们将与其他框架的维护者合作,以实现相同的集成。如果您构建 Cloudflare 框架适配器,Workers 缓存 API 正好适配您的需求,基于标头的配置、程序化清除,无需对平台特定的概念进行建模。

在与 Worker 相同的仪表板中查看缓存信息

只有当您能够看到缓存的运行情况时,它才真正有用。Workers Observability 仪表板现在会显示每次调用的缓存命中信息:

BLOG-3262 image6

您可以查看每个 Worker 的:

  • 高速缓存命中率随时间的变化。启用缓存后,您希望看到数值上升的趋势。
  • 命中、未命中、更新、绕过次数的细分。如果命中率低,可以在这里找到原因:BYPASS 响应过多(因为某些程序正在设置 Cookie?)、MISS 响应过多(因为缓存键的分区比预想的更多?),或是 UPDATING 响应过多(因为 max-age 比流量间隔时间更短?)。

由于这些信息与 Worker 的其他可观测性(日志、异常、CPU 时间、请求计数)都在同一个仪表板中,因此,您不必在查看区域和 Worker 之间切换上下文就能了解正在发生的事情。

账单

缓存命中不会运行 Worker,CPU 时间也不会计费。它们按标准 Workers 请求率计费,与其他任何调用相同。缓存未命中与绕过会正常计费(请求 + CPU 时间),与不使用缓存时完全相同。

Outcome

Request charge

CPU time charge

缓存 HIT(Worker 不运行)

标准费率

不计费

缓存 MISS(Worker 运行)

标准费率

计费

缓存 BYPASS(Worker 运行)

标准费率

计费

静态资产请求

标准费率

不计费

Worker 之间的调用

标准费率

如果 Worker 运行,则收费

没有单独的 Workers 缓存 SKU,也没有按 GB 计费的缓存存储费用。分层缓存、清除、stale-while-revalidate 以及上述分析功能均已包含。如果请求原本会触发 Worker 运行,但 Workers 缓存将其作为缓存命中处理,您仍需支付标准请求费用,但无需为请求的 CPU 时间付费。因此,缓存命中比在 Worker 中渲染相同响应的成本更低。

需要注意的是:启用缓存后,通常免费的请求(例如静态资产请求 以及通过服务绑定或 ctx.exports 进行的 Worker 之间的调用)将按标准费率计费,因为这些请求现在都会查询 Worker 前端的缓存。

下一步

我们接下来要做的事情:

  • 利用 Smart Placement 实现更智能的托管。目前,Cloudflare 会分别选择上层缓存和 Smart Placement 缓存目标。在完全未命中的情况下,请求可能会在 Cloudflare 各个位置之间往返两次:一次用于检查上层缓存,另一次用于在靠近数据的地方运行 Worker。我们正在努力协调这些选择,以便缓存未命中时,请求只需进行一次长途传输。
  • 提高响应大小限制。在发布初期,所有响应都遵循 Free 计划的可缓存大小限制 (512 MB),无论账户类型是什么。这只是暂时的措施,一旦我们完成一些推广步骤,将执行每种计划的标准缓存限制。
  • 添加更多框架集成。Astro 具有内置的 Workers 缓存集成。我们将与维护者合作,通过 Vinext 将类似的集成添加到其他框架中,包括 TanStack Start 和 Next.js。
  • 开发用于标记缓存响应过期的 API。ctx.cache.purge() 会从缓存中移除匹配的响应。我们将开发 ctx.cache.invalidate() API,使匹配的响应表现为已过期,这样一来,即使 Worker 在后台刷新缓存,下一个请求仍然可以通过 stale-while-revalidate 快速获取过期的响应。

立即试用

Workers 缓存现已面向所有计划中的每一个 Worker 开放。

若要开始使用,请将 "cache": { "enabled": true } 添加到 wrangler.jsonc 文件,重新部署,然后开始设置 Cache-Control 标头。Workers 缓存文档详细介绍了完整的功能,包括快速入门缓存键清除组合模式和示例,以及调试

过去,Workers 在缓存的前端运行。现在,它们也可以在缓存后端运行。您可以根据需要,选择在任意一侧运行,或者通过服务绑定,在缓存两侧同时运行。

我们迫不及待想看到您构建的成果。