跳转到内容
搜索文档

Workers 缓存

最后更新 查看 MarkdownAgent 设置

Workers Cache 允许 Cloudflare 直接从缓存返回 HTTP 响应,而无需执行您的 Worker 代码。当传入请求与已缓存的响应匹配时,Cloudflare 会直接从边缘缓存提供该响应——从而降低延迟并减少 Workers CPU 用量。

缓存适用于 Worker 的任何 fetch() 调用——包括 eyeball 请求(来自浏览器和 API 客户端的请求)、通过 service bindings(绑定) 发送的请求,以及通过 ctx.exports 在 entrypoint 之间进行的 loopback fetch() 调用。您可以通过响应上的标准 HTTP Cache-Control 指令来控制缓存。

您的 Worker 缓存

Workers Cache 是您的 Worker 的缓存。它由您的 Worker 拥有、由您的 Worker 操作,且仅对您的 Worker 私有。

Worker 是一种无 zone 的实体——Worker 可以绑定到任意数量的 zone(站点区域),在 workers.dev 上运行,或完全通过 service bindings 调用而无需接触 zone。缓存跟随 Worker,而非 zone,因此:

  • 任何 zone 的缓存配置都不适用于 Workers Caching。 Cache RulesCache Response RulesPage Rules、缓存级别设置、zone 的 default cached-file-extensions 列表,以及所有其他 zone 级别的缓存控制,对 Worker 的缓存均无影响。
  • 您的 Worker 拥有完全控制权。 您在响应上设置 Cache-Control 标头,Cloudflare 会按照 RFC 9111 遵循它们。这就是全部的配置界面。
  • 缓存在 Worker 的所有调用方式之间共享。 绑定到 api.example.comapi.example.net 并通过 service binding 调用的 Worker,会为这三种方式提供相同的缓存响应——缓存键由请求路径、entrypoint、ctx.props 以及(默认情况下)Worker 版本决定,而非主机名。请参阅 Cache keys

Worker 即配置界面

Worker 本身已经具备无限的可定制性。您可以更改响应正文、重写标头、根据任意请求属性分支、通过 service bindings 或 ctx.exports 调用其他 Worker,并在整个系统中组合逻辑。

Workers Caching 正是基于这一点。它不会引入单独的缓存行为配置层,而是让您的 Worker 直接表达该意图——通过返回的 Cache-Control 标头、接受的 ctx.props,以及发出的程序化清除操作。您想要配置的缓存行为,都可以在代码中完成:

  • 希望某些路径有更长的 TTL?在 Worker 中根据路径分支,设置不同的 max-age
  • 希望在缓存前移除跟踪查询参数?在 gateway Worker 中重写 URL 或 ctx.props,然后再分发。
  • 希望按租户划分缓存?在 ctx.props 中设置租户标识符——它会进入缓存键。
  • 希望为已认证用户绕过缓存?返回 Cache-Control: private,或依赖 Set-CookieAuthorization 触发的自动绕过

您已经编写的 Worker 就是配置机制。Workers Caching 在其前面运行,并遵循 Worker 返回的任何标头。

何时适合使用缓存

缓存适合以下 Worker:

  • 执行 CPU 密集型工作,且结果可在多个请求之间复用——内容生成、模板渲染、数据转换。
  • 从慢速源站或第三方 API 获取数据,并希望为后续请求吸收该延迟。
  • 为服务端渲染或静态生成的站点提供支持,其中许多请求产生相同的响应。

缓存对每次请求都会变化的按用户响应、非幂等操作(POSTPUTDELETE),或必须每次重新计算的响应没有帮助。

工作原理

启用缓存后,Cloudflare 会在运行 Worker 之前检查缓存。命中时,直接返回缓存的响应。未命中时,Worker 会运行;如果响应根据其 Cache-Control 标头可缓存,Cloudflare 会将其存储以供下次请求使用。

flowchart LR
    accTitle: Cache before a Worker request flow
    accDescr: Request arrives at Cloudflare, cache is consulted before Worker execution.

    Request["Request"] --> Cache{"Cache"}
    Cache -- Hit --> Response["Cached response returned"]
    Cache -- Miss --> Worker["Worker runs"]
    Worker --> Store["Response stored in cache"]
    Store --> Response2["Response returned"]

分层缓存

Workers Caching 默认采用分层结构。Cloudflare 为您的 Worker 运行两层缓存:

  • 下层(Lower tier) — 位于最接近 eyeball 的 Cloudflare 数据中心中的缓存。每个接收 Worker 流量的数据中心都有自己的下层缓存。
  • 上层(Upper tier) — 一组数量较少的数据中心,每个下层在未命中时都会查询它们。上层会聚合整个网络的缓存填充。

如果下层命中,请求会从下层提供。如果下层未命中,下层会询问上层。如果上层也未命中,Worker 才会运行以生成响应——该响应在返回途中会存储在两层中,因此来自任何数据中心的后续请求都能受益。

flowchart LR
    accTitle: Tiered cache for Workers
    accDescr: A request hits the lower-tier cache first, then the upper-tier cache, then the Worker.

    Request["Request"] --> Lower{"Lower-tier cache<br/>(near eyeball)"}
    Lower -- Hit --> Response["Cached response returned"]
    Lower -- Miss --> Upper{"Upper-tier cache"}
    Upper -- Hit --> Lower
    Upper -- Miss --> Worker["Worker runs"]
    Worker --> Upper

这与为 zone 提供支持的 Tiered Cache 采用相同的拓扑结构,并自动应用于您的 Worker。您无需配置,无论 Worker 是否使用 Smart Placement,分层都会运行。

为什么这很重要: 地球上任意位置对给定缓存键的首次请求都会填充上层。之后来自任何 Cloudflare 数据中心的请求都可以从上层提供,而无需运行 Worker——即使该位置的下层从未见过该请求。缓存命中率远高于单层扁平缓存。

请求合并

当同一缓存键的大量请求同时到达 Cloudflare 数据中心且响应尚未缓存时,Cloudflare 只会运行 Worker 一次,并将生成的响应提供给所有等待中的请求。这与 zone 缓存使用的请求合并机制相同,并自动应用于 Workers Caching。等待中的请求会在每个缓存键的缓存锁上阻塞,直到第一个请求产生响应。

flowchart LR
    accTitle: Cache request collapsing for Workers
    accDescr: Many simultaneous requests for the same cache key produce one Worker invocation; all requests receive the same response.

    R1["Request 1"] --> Lock
    R2["Request 2"] --> Lock
    R3["Request 3"] --> Lock
    Rn["..."] --> Lock
    Lock{"Cache lock<br/>(per cache key, per data center)"}
    Lock -- "first request" --> Worker["Worker runs once"]
    Worker --> Response["Response<br/>streamed to all<br/>waiting requests"]

为什么这很重要: 如果没有请求合并,对全新 URL 的突发流量会为每个请求调用一次 Worker,成倍增加 CPU 计费以及 Worker 所调用的任何后端的负载。有了请求合并,该突发仍然只会产生一次 Worker 调用。

需要注意的几点:

  • 合并按缓存键、按数据中心进行。 产生不同缓存键的请求不会彼此合并。两个同时未命中的数据中心各自会运行 Worker 一次(上层会进一步整合;请参阅分层缓存)。
  • 流式响应也会被合并。 等待中的请求会加入正在进行的响应流,以便在生成正文时接收数据——它们不必等待完整响应才开始返回字节。
  • 合并不适用于不可缓存的响应。 如果 Worker 的响应不可缓存(BYPASSDYNAMIC),每个请求都会获得独立的调用。缓存只会合并产生允许存储的响应的请求。

这是 Workers Caching 与 Cache API 之间最显著的区别之一——Cache API 不会合并并发请求,因此对全新 URL 的突发流量会为每个请求调用一次 Worker。

快速入门

本快速入门将引导您启用缓存、部署并观察缓存的实际效果。

1. 在 Wrangler 配置中启用缓存

{
 "name": "my-worker",
 "main": "src/index.ts",
 // Set this to today's date
 "compatibility_date": "2026-08-17",
 "cache": {
  "enabled": true,
 },
}
name = "my-worker"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-08-17"

[cache]
enabled = true

2. 从 Worker 返回可缓存的响应

使用 max-age 控制 Cloudflare 缓存每个响应的时长:

src/index.jsjs
export default {
	async fetch(request) {
		const body = JSON.stringify({
			timestamp: new Date().toISOString(),
			random: Math.random(),
		});

		return new Response(body, {
			headers: {
				"Content-Type": "application/json",
				// Cache for 1 hour; serve stale for up to 5 minutes while revalidating.
				"Cache-Control": "public, max-age=3600, stale-while-revalidate=300",
			},
		});
	},
};
src/index.tsts
export default {
	async fetch(request): Promise<Response> {
		const body = JSON.stringify({
			timestamp: new Date().toISOString(),
			random: Math.random(),
		});

		return new Response(body, {
			headers: {
				"Content-Type": "application/json",
				// Cache for 1 hour; serve stale for up to 5 minutes while revalidating.
				"Cache-Control": "public, max-age=3600, stale-while-revalidate=300",
			},
		});
	},
} satisfies ExportedHandler;

3. 部署并观察缓存

部署 Worker:

npx wrangler deploy

然后发送两次请求并查看 Cf-Cache-Status 响应标头:

curl -I https://my-worker.example.workers.dev/
首次请求 — 预期结果txt
HTTP/2 200
cache-control: public, max-age=3600, stale-while-revalidate=300
cf-cache-status: MISS
curl -I https://my-worker.example.workers.dev/
第二次请求 — 预期结果txt
HTTP/2 200
cache-control: public, max-age=3600, stale-while-revalidate=300
cf-cache-status: HIT

第二次请求会收到缓存的响应。两次请求正文中的 timestamprandom 值相同,尽管 Worker 每次运行都会生成新值——这证实了第二次请求并未执行您的 Worker。

哪些内容会被缓存

  • 对 Worker fetch 处理程序的 HTTP 调用符合缓存条件,包括 eyeball 请求、service binding fetch() 调用,以及通过 ctx.exports 进行的 loopback fetch() 调用。
  • 仅缓存 GETHEAD 请求。其他方法始终会调用 Worker。同一 URL 的 GETHEAD 共享单个缓存条目——请参阅 Cache keys
  • 只有 fetch() 调用会经过缓存。 WorkerEntrypoint 上的自定义 RPC 方法(例如 ctx.exports.Backend.getUser(id))会完全绕过缓存并始终运行被调用方。要缓存某项工作,请将其作为独立 entrypoint 上的 fetch 处理程序公开。
  • WebSocket 升级请求会绕过缓存。 携带 Upgrade: websocketGET 请求始终会调用 Worker。
  • 其他调用类型——scheduled(Cron Triggers)、queue 消费者、WorkflowsTail WorkersDurable Object 调用、Email Workers——始终运行且不涉及缓存。
  • 可缓存性由 Worker 返回的响应标头决定。Workers Caching 遵循 RFC 9111 中定义的语义,包括对未携带 Cache-Control 的响应进行启发式新鲜度判断。请参阅 Cache-Control 了解 Cloudflare 遵循的完整指令列表。
  • Cloudflare 的标准缓存绕过条件适用。特别是,带有 Set-Cookie 标头的响应以及带有 Authorization 标头的请求会触发自动绕过。
  • 支持 Preview URLs。每个预览与生产部署独立缓存,因此在预览中测试影响缓存的更改不会影响生产的缓存响应。
  • 支持 Workers for Platforms。每个用户 Worker 都有自己的缓存,与调度器以及命名空间中的其他用户 Worker 隔离。

Cf-Cache-Status 响应标头会告诉您每个请求的处理情况。 您最常看到的值包括 HITMISSEXPIREDREVALIDATEDUPDATINGSTALEBYPASS。请参阅 Cloudflare cache responses 了解完整值集。

使用 Vary 进行内容协商

Workers Caching 遵循 RFC 9110RFC 9111 中定义的 Vary 响应标头。当 Worker 返回 Vary 标头时,Cloudflare 会为所列请求标头值的不同组合分别存储缓存变体,且仅当传入请求的标头与存储该变体时使用的标头匹配时才返回缓存变体。

这样,单个 URL 可以缓存多种表示形式——例如不同的编码、不同的内容类型或不同的语言——而无需 Worker 手动协调内容协商:

src/index.jsjs
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",
			},
		});
	},
};
src/index.tsts
export default {
	async fetch(request): Promise<Response> {
		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",
			},
		});
	},
} satisfies ExportedHandler;

说明:

  • Vary: * 会禁用该响应的缓存。通配符变体无法从请求标头中确定性地满足,因此 Cloudflare 不会存储该响应。
  • 变体在清除目的上共享单个缓存条目——清除 匹配任何变体的标签或路径前缀会使该 URL 的所有变体失效。因此,URL 的所有变体必须使用相同的 Cache-Tag 值。
  • Vary 与已自行产生变体的图像转换功能(Polish、Image Resizing)不兼容。被这些功能重写的响应会忽略 Vary
  • 变体按确切的请求标头值存储。发送语义等价但文本不同的值的客户端——例如 Accept-Encoding: gzip, brAccept-Encoding: br, gzip——会产生单独的变体。如果需要减少变体扩散,请调整 Worker 看到的标头(例如,在 gateway Worker 中规范化后再传递请求)。

Worker 之间的缓存

当一个 Worker 通过 service binding 调用另一个 Worker 时,会查询被调用方的缓存。如果被调用方启用了缓存且有匹配的缓存响应,调用方会收到该响应而无需调用被调用方。

flowchart LR
    accTitle: Cache between Workers
    accDescr: Worker A calls Worker B; Worker B's cache is consulted before Worker B runs.

    Request["Request"] --> WorkerA["Worker A"]
    WorkerA --> CacheB{"Worker B's cache"}
    CacheB -- Hit --> WorkerA
    CacheB -- Miss --> WorkerB["Worker B"]
    WorkerB --> CacheB

service binding 调用的缓存键包含调用方的 ctx.props,因此具有不同授权上下文的调用方会分别缓存。详情请参阅 Cache keys

对于同一账户内的调用,调用 Worker 还可以通过设置 cf.cacheKey 覆盖缓存键,或设置 cf.cacheControl 提供 Cache-Control 指令,为单个请求定制被调用方的缓存行为。

缓存 Durable Object 响应

Durable Objects 不会被 Workers Caching 直接缓存。但是,由于 Workers Caching 在任何 Worker entrypoint 前面运行,您可以通过将 Durable Object 封装在 named Worker entrypoint 后面并缓存该 entrypoint,来缓存 Durable Object 的 HTTP 响应。

封装 entrypoint 将请求转发到 Durable Object,并在返回的响应上设置 Cache-Control。由于 Workers Caching 位于 entrypoint 前面,后续请求会从缓存提供,而无需再次进入 Durable Object。

此处的默认 entrypoint 是一个应在每次请求上运行的 gateway,因此在其上禁用缓存,并在 CachedCounter 上启用(请参阅按 entrypoint 缓存):

{
	"name": "my-worker",
	"main": "src/index.ts",
	// Set this to today's date
	"compatibility_date": "2026-08-17",
	"cache": { "enabled": true },
	"exports": {
		"default": { "type": "worker", "cache": { "enabled": false } },
		"CachedCounter": { "type": "worker", "cache": { "enabled": true } },
	},
}
name = "my-worker"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-08-17"

[cache]
enabled = true

[exports.default]
type = "worker"

  [exports.default.cache]
  enabled = false

[exports.CachedCounter]
type = "worker"

  [exports.CachedCounter.cache]
  enabled = true
src/index.jsjs
import { WorkerEntrypoint } from "cloudflare:workers";

// Cached entrypoint. Requests to this entrypoint are served from cache
// when possible; on a miss, the Durable Object is invoked and its
// response is stored.
export class CachedCounter extends WorkerEntrypoint {
	async fetch(request) {
		const id = this.env.COUNTER.idFromName("global");
		const stub = this.env.COUNTER.get(id);
		const response = await stub.fetch(request);

		// Attach cache headers. Clone into a new Response so the headers
		// are mutable.
		return new Response(response.body, {
			status: response.status,
			headers: {
				...Object.fromEntries(response.headers),
				"Cache-Control": "public, max-age=30",
			},
		});
	}
}

// Default entrypoint. Delegates to the cached entrypoint via ctx.exports,
// which routes through the cache.
export default {
	async fetch(request, env, ctx) {
		return ctx.exports.CachedCounter.fetch(request);
	},
};
src/index.tsts
import { WorkerEntrypoint } from "cloudflare:workers";

interface Env {
	COUNTER: DurableObjectNamespace;
}

// Cached entrypoint. Requests to this entrypoint are served from cache
// when possible; on a miss, the Durable Object is invoked and its
// response is stored.
export class CachedCounter extends WorkerEntrypoint<Env> {
	async fetch(request: Request): Promise<Response> {
		const id = this.env.COUNTER.idFromName("global");
		const stub = this.env.COUNTER.get(id);
		const response = await stub.fetch(request);

		// Attach cache headers. Clone into a new Response so the headers
		// are mutable.
		return new Response(response.body, {
			status: response.status,
			headers: {
				...Object.fromEntries(response.headers),
				"Cache-Control": "public, max-age=30",
			},
		});
	}
}

// Default entrypoint. Delegates to the cached entrypoint via ctx.exports,
// which routes through the cache.
export default {
	async fetch(request, env, ctx): Promise<Response> {
		return ctx.exports.CachedCounter.fetch(request);
	},
} satisfies ExportedHandler<Env>;

有关将 gateway entrypoint 与缓存内部 entrypoint 组合使用的更多模式,请参阅 Examples

Smart Placement 与缓存

Smart Placement 决定 Worker 运行在何处运行——通常更接近慢速源站或数据库。它不会移动缓存。Workers Caching 始终在 eyeball 附近设有下层,并在网络上聚合上层,与上文分层缓存所述完全一致,无论是否启用 Smart Placement。

缓存在考虑 Smart Placement 之前始终会被查询。具体来说:

  • 下层命中: 从最接近 eyeball 的数据中心返回响应。Worker 不会运行。不会考虑 Smart Placement。
  • 下层未命中、上层命中: 从上层返回响应。Worker 不会运行。不会考虑 Smart Placement。
  • 两层均未命中: Smart Placement 将 Worker 的执行路由到放置目标(例如,靠近源站)。生成的响应在返回 eyeball 的途中会存储在两层缓存中。

重要的是,上层与 Smart Placement 目标是独立的位置。上层由 Cloudflare 选择以聚合整个网络的缓存填充;Smart Placement 目标则选择以最小化 Worker 与其后端之间的延迟。它们通常不在同一数据中心。

flowchart LR
    accTitle: Tiered cache with Smart Placement across three locations
    accDescr: The eyeball, the upper-tier cache, and the Smart Placement target are three independent locations. Requests traverse them in order on a full cache miss.

    subgraph EyeballColo["Data center near eyeball"]
        Request["Request"] --> Lower{"Lower-tier cache"}
    end

    subgraph UpperColo["Upper-tier data center"]
        Upper{"Upper-tier cache"}
    end

    subgraph PlacedColo["Smart Placement target"]
        Placed["Worker runs"]
        Origin["Origin / backend"]
        Placed <--> Origin
    end

    Lower -- Hit --> Response["Response"]
    Lower -- Miss --> Upper
    Upper -- Hit --> Lower
    Upper -- Miss --> Placed
    Placed --> Upper

因此在完全缓存未命中时,请求会经过三个位置:eyeball 附近的下层数据中心、上层数据中心,以及 Smart Placement 目标。缓存层会吸收这一成本,使整个网络只需支付一次到放置目标的慢速往返——上层会屏蔽放置目标,使其免受每个下层未命中的影响。

清除缓存

Worker 可以随时使用 ctx.cache.purge() 使自身缓存失效。标签是最灵活的机制——在返回响应时用 Cache-Tag 标记,稍后清除这些标签:

src/index.jsjs
export default {
	async fetch(request, env, ctx) {
		await ctx.cache.purge({ tags: ["blog-posts"] });
		return new Response("Purged", { status: 200 });
	},
};
src/index.tsts
export default {
	async fetch(request, env, ctx): Promise<Response> {
		await ctx.cache.purge({ tags: ["blog-posts"] });
		return new Response("Purged", { status: 200 });
	},
} satisfies ExportedHandler;

当您没有 ctx 作用域时——例如,从工具模块中——也可以cloudflare:workers 导入 cache 并调用 cache.purge({...})。有关所有清除模式和用法,请参阅 Purging the cache

定价

Workers Cache 没有单独的定价。启用 Workers Cache 后,对 Worker 的所有请求均按标准 Workers 请求费率 计费——与对 Worker 的任何其他请求相同的按请求费率——无论响应来自缓存还是来自 Worker。除标准请求费率外没有其他费用。仅在 Worker 运行时才计费 CPU 时间——缓存命中不消耗 CPU 时间。

请求类型 请求费用 CPU 时间费用
缓存 HIT(Worker 未运行) 标准费率 不计费
缓存 MISS(Worker 运行) 标准费率 计费
缓存 BYPASS(Worker 运行) 标准费率 计费
静态资源请求 标准费率 不计费
Worker 间调用 标准费率 Worker 运行时计费

有关示例,请参阅 定价示例:启用缓存的 Worker

后续步骤

这篇文档对您有帮助吗?