Skip to main content
On this page

CDN 与缓存

Deno Deploy 包含一个内置的 HTTP 缓存层,可以在边缘自动缓存符合条件的响应,从而降低延迟和源服务器负载。本文档介绍缓存的工作原理、如何控制缓存行为以及如何失效缓存内容。

缓存工作原理 Jump to heading

当请求到达 Deno Deploy 时:

  1. 缓存会检查是否存在该请求的有效缓存响应
  2. 若找到且缓存未过期,立即返回缓存响应(缓存命中)
  3. 若未找到或缓存过期,请求会转发给您的应用(缓存未命中)
  4. 可缓存的响应会被存储以满足未来请求

该缓存遵循 RFC 9110RFC 9111 规范,实现标准 HTTP 缓存行为。

Cache-Control 头 Jump to heading

Deno Deploy 支持标准的 Cache-Control 头中的以下指令:

响应指令 Jump to heading

指令 说明
public 响应可被共享缓存缓存
private 响应是用户特定的,不能缓存(绕过 CDN)
no-store 响应不应被缓存
no-cache 响应使用前必须重新验证
max-age=N 响应在 N 秒内被视为新鲜
s-maxage=N 类似 max-age,但只适用于共享缓存(优先级更高)
stale-while-revalidate=N 在后台重新验证时可提供陈旧内容
stale-if-error=N 若源返回错误,可提供陈旧内容
must-revalidate 过期响应不允许使用,必须重新验证

示例:边缘缓存 1 小时 Jump to heading

Deno.serve(() => {
  return new Response("Hello, World!", {
    headers: {
      "Cache-Control": "public, s-maxage=3600",
    },
  });
});

示例:使用 stale-while-revalidate 缓存 Jump to heading

Deno.serve(() => {
  return new Response(JSON.stringify({ data: "..." }), {
    headers: {
      "Content-Type": "application/json",
      // 缓存 60 秒,后台重新验证时最多提供 5 分钟的陈旧内容
      "Cache-Control": "public, s-maxage=60, stale-while-revalidate=300",
    },
  });
});

Deno Deploy 专用头 Jump to heading

Deno Deploy 支持额外的 headers 以实现更细粒度的缓存控制:

Deno-CDN-Cache-Control Jump to heading

一个 CDN 专用的缓存控制头,其优先级高于 CDN-Cache-ControlCache-Control。当您希望 Deno Deploy 的 CDN 缓存策略与浏览器或其他缓存不同步时使用此头。

头优先级(高到低):

  1. Deno-CDN-Cache-Control
  2. CDN-Cache-Control
  3. Cache-Control
Deno.serve(() => {
  return new Response("Hello!", {
    headers: {
      // 浏览器缓存 60 秒
      "Cache-Control": "public, max-age=60",
      // Deno Deploy CDN 缓存 1 小时
      "Deno-CDN-Cache-Control": "public, s-maxage=3600",
    },
  });
});

Deno-Cache-Tag / Cache-Tag Jump to heading

将响应关联到缓存标签以便目标失效。标签允许您无需知道具体 URL 即可失效一组缓存响应。

Deno.serve((req) => {
  const url = new URL(req.url);
  const productId = url.pathname.split("/")[2];

  return new Response(JSON.stringify({ id: productId, name: "Widget" }), {
    headers: {
      "Content-Type": "application/json",
      "Cache-Control": "public, s-maxage=3600",
      // 为该响应打标签,方便稍后失效
      "Deno-Cache-Tag": `product-${productId},products,catalog`,
    },
  });
});

标签格式:

  • 多个标签以逗号分隔
  • 标签忽略大小写
  • 每个标签最多 1024 字符
  • 每个响应最多 500 个标签
  • 标签必须使用 UTF-8 编码

Deno-Cache-Id Jump to heading

一个特殊的头,具有两个功能:

  1. 选择退出自动失效:带有此头的响应使用一个共享的缓存命名空间,缓存会跨部署持久存在。没有这个头,缓存响应会在部署新版本时自动失效。

  2. 作为缓存标签:其值可用于失效该缓存响应。

Deno.serve(() => {
  return new Response("Static content that rarely changes", {
    headers: {
      "Cache-Control": "public, s-maxage=86400",
      // 此响应在重新部署后依然保留
      "Deno-Cache-Id": "static-content-v1",
    },
  });
});

Deno-Cache-Id 使用场景:

  • 应该跨部署保留缓存的内容(如带内容哈希的静态资源)
  • 长期缓存响应,需要显式失效控制
  • 在不同部署版本间共享缓存响应

Deno-Vary Jump to heading

扩展缓存变体支持,超出标准 HTTP Vary 语义。(即将支持)

缓存失效 Jump to heading

Deno Deploy 支持按需通过缓存标签失效缓存,允许您无需重新部署即清理特定缓存内容。

失效 API Jump to heading

从您的 Deno Deploy 应用内向 http://cache.localhost/invalidate/http 发送 POST 请求:

Deno.serve(async (req) => {
  const url = new URL(req.url);

  if (req.method === "POST" && url.pathname === "/admin/purge") {
    // 失效所有标记为 "products" 的响应
    const res = await fetch("http://cache.localhost/invalidate/http", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        tags: ["products"],
      }),
    });

    if (res.ok) {
      return new Response("Cache purged successfully");
    }
    return new Response("Purge failed", { status: 500 });
  }

  // ... 处理其他请求
});

失效请求格式 Jump to heading

{
  "tags": ["tag1", "tag2", "tag3"]
}
  • tags:需要失效的缓存标签数组(必填)
  • 单次请求最多 500 个标签

通配符失效 Jump to heading

使用 "*" 可失效您部署的所有缓存响应:

await fetch("http://cache.localhost/invalidate/http", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    tags: ["*"],
  }),
});

跨区域失效 Jump to heading

缓存失效会自动同步到所有 Deno Deploy 区域,失效操作在几秒内全局生效。

示例:内容管理系统 Webhook Jump to heading

Deno.serve(async (req) => {
  const url = new URL(req.url);

  // CMS 更新的 Webhook 端点
  if (req.method === "POST" && url.pathname === "/webhook/cms") {
    const payload = await req.json();

    // 根据内容类型选择失效标签
    const tags: string[] = [];

    if (payload.type === "product") {
      tags.push(`product-${payload.id}`, "products");
    } else if (payload.type === "category") {
      tags.push(`category-${payload.id}`, "categories", "navigation");
    }

    if (tags.length > 0) {
      await fetch("http://cache.localhost/invalidate/http", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ tags }),
      });
    }

    return new Response("OK");
  }

  // ... 服务内容
});

缓存状态头 Jump to heading

Deno Deploy 会添加 Cache-Status 响应头,指示缓存结果:

说明
deno; hit 响应来自缓存
deno; fwd=uri-miss; stored 缓存未命中,响应已存储
deno; fwd=miss; stored Vary 键未命中,响应以新 vary 键存储
deno; fwd=stale 响应过期,已转发到源服务器
deno; fwd=method 非缓存请求方法(如 POST、PUT 等)
deno; fwd=bypass 明确绕过缓存
deno; fwd=request 请求指令阻止缓存

绕过细节 Jump to heading

当响应绕过缓存时,detail 字段说明原因:

  • detail=not-cacheable - 响应不符合缓存条件
  • detail=no-cache-or-private - 具备 no-cacheprivate 指令
  • detail=set-cookie - 响应含 Set-Cookie
  • detail=pragma-no-cache - 旧式的 Pragma: no-cache 头存在
  • detail=too-large - 响应体过大,超出可缓存限制
  • detail=zero-ttl - 计算出的缓存时长为零
  • detail=vary-star - Vary: * 阻止缓存
  • detail=header-overflow - 响应头过多

可缓存的响应 Jump to heading

响应满足以下条件时可缓存:

  1. 请求方法是 GETHEAD
  2. 响应状态码为可缓存(200、203、204、206、300、301、308、404、405、410、414、501)
  3. 响应包含缓存相关头(如 Cache-ControlExpires 等)
  4. 没有 no-storeprivateno-cache 指令
  5. 响应不包含 Set-Cookie
  6. 响应体大小在可缓存范围内

缓存变体(Vary) Jump to heading

缓存支持标准的 Vary 头,根据请求头存储响应的不同版本:

Deno.serve((req) => {
  const acceptLanguage = req.headers.get("Accept-Language") || "en";
  const language = acceptLanguage.startsWith("es") ? "es" : "en";

  return new Response(`Hello in ${language}!`, {
    headers: {
      "Cache-Control": "public, s-maxage=3600",
      "Vary": "Accept-Language",
      "Content-Language": language,
    },
  });
});

注意: Vary: * 会完全阻止缓存。

HEAD 请求 Jump to heading

HEAD 请求可使用缓存的 GET 响应,缓存会自动剥离响应体,仅返回头信息。

范围请求 Jump to heading

缓存支持 HTTP 范围请求(Range 头),可以基于缓存的完整响应返回部分内容。

最佳实践 Jump to heading

1. 使用 s-maxage 实现 CDN 缓存 Jump to heading

// 让浏览器缓存 1 分钟,CDN 缓存 1 小时
headers: {
  "Cache-Control": "public, max-age=60, s-maxage=3600"
}

2. 给内容打标签以实现目标失效 Jump to heading

// 按内容类型、ID 和分类打标签,实现灵活清理
headers: {
  "Deno-Cache-Tag": `article-${id},articles,category-${category}`
}

3. 使用 stale-while-revalidate 优化用户体验 Jump to heading

// 在后台刷新时提供陈旧内容
headers: {
  "Cache-Control": "public, s-maxage=60, stale-while-revalidate=600"
}

4. 对稳定资源使用 Deno-Cache-Id Jump to heading

// 对不应随着部署失效的内容寻址资源
const hash = computeHash(content);
headers: {
  "Cache-Control": "public, s-maxage=31536000, immutable",
  "Deno-Cache-Id": `asset-${hash}`
}

5. 设置合适的缓存时长 (TTL) Jump to heading

  • 文件名中带哈希的静态资源:max-age=31536000(一年)
  • API 响应:s-maxage=60s-maxage=300,配合 stale-while-revalidate
  • 个性化内容:不缓存或合理使用 Vary

自动缓存失效 Jump to heading

默认情况下,所有没有 Deno-Cache-Id 的缓存响应,在您部署新版本应用时会自动失效,确保用户始终看到最新内容。

如果想关闭自动失效,可以使用 Deno-Cache-Id 头。

你找到了你需要的东西吗?

编辑此页面
隐私政策