CDN 边缘缓存

CDN 边缘缓存的控制指令(s-maxage、CDN-Cache-Control、Surrogate-Control)、缓存键和 Purge 策略。

#tech / dev / frontend #type / concept #status / evergreen #platform / browser #tech / network #tech / dev / deploy

[!info] related notes

CDN 边缘缓存

一句话定义

CDN 缓存把响应存储在靠近用户的边缘服务器上,由独立于浏览器的缓存指令控制,允许 CDN 和浏览器使用不同的缓存策略。

s-maxage

Cache-Control: max-age=300, s-maxage=86400
  • s-maxage 仅对共享缓存(CDN、代理)生效
  • 浏览器忽略 s-maxage,只看 max-age
  • s-maxage 存在时,共享缓存必须用它替代 max-age
  • 定义在 RFC 9111

典型场景

# 浏览器缓存 5 分钟,CDN 缓存 1 天
Cache-Control: max-age=300, s-maxage=86400, public

# 浏览器不缓存,CDN 缓存 1 小时
Cache-Control: no-cache, s-maxage=3600, public

CDN-Cache-Control

Cache-Control: max-age=300
CDN-Cache-Control: max-age=86400
  • 更新的 CDN 专用头部,Cloudflare、Fastly 等支持
  • 浏览器完全忽略 CDN-Cache-Control
  • 优点:不影响 Cache-Control 头部,浏览器和 CDN 的策略完全解耦

对比

头部浏览器CDN
Cache-Control: max-age=300
s-maxage=86400忽略看(替代 max-age)
CDN-Cache-Control: max-age=86400忽略看(独立指令)

Surrogate-Control

Surrogate-Control: max-age=86400
  • Akamai 和部分旧 CDN 使用
  • 功能类似 CDN-Cache-Control,但更早出现
  • CDN 在转发响应给浏览器前会移除这个头部

public vs private

Cache-Control: private, max-age=600    # 只有浏览器能缓存
Cache-Control: public, max-age=600     # 浏览器和 CDN 都能缓存
  • private:共享缓存不得存储此响应
  • public:任何缓存节点都可以存储
  • 未指定时:如果响应是针对特定用户的(有 Authorization 头),默认 private

常见错误

# 错误:包含用户信息的 API 响应设为 public
Cache-Control: public, max-age=300    # CDN 会缓存,其他用户可能拿到

# 正确:
Cache-Control: private, max-age=300   # 只有浏览器缓存

CDN 缓存键

CDN 默认用完整 URL(包括 query string)作为缓存键:

缓存键 = https://example.com/app.js?v=123

常见配置

  • 包含 query string:默认行为,app.js?v=1app.js?v=2 是不同缓存条目
  • 忽略 query string:部分 CDN 可配置,app.js?v=1app.js?v=2 视为同一资源
  • 自定义 key:可用 CDN 配置添加/移除头部到缓存键

Vary 的影响

CDN 必须尊重 Vary 头部,但支持程度因 CDN 而异:

CDNVary 支持
Cloudflare部分支持,可配置
Fastly完整支持,需显式声明
Akamai有限支持
AWS CloudFront默认忽略(可配置)

详见 → Vary 与缓存键

Purge 策略

Soft Purge(软清除)

  • 标记缓存为”过期”,但不立即删除
  • 下次请求时 CDN 会向源站验证(类似协商缓存)
  • 适合内容可能回滚的场景

Hard Purge(硬清除)

  • 立即从 CDN 边缘删除缓存
  • 下次请求一定回源
  • 适合安全补丁、紧急修复

API Purge

大多数 CDN 提供 API 或控制台进行清除:

# Cloudflare API 示例
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
  -H "Authorization: Bearer {token}" \
  -d '{"files":["https://example.com/app.js"]}'

为什么”purge all”代价高

  • 清除所有缓存 → CDN 所有边缘节点回源
  • 源站瞬间承受巨大流量
  • 应该只 purge 变化的资源

Age 头部与 CDN

CDN 缓存资源后,响应中的 Age 头部会累加:

源站 → CDN(缓存 10 分钟)→ 浏览器
响应:Age: 600

浏览器看到 Age: 600,会从 max-age 中扣除已消耗的时间。详见 → Age、Date 与新鲜度计算

ESI(Edge Side Includes)

CDN 边缘服务器的页面片段缓存技术:

<esi:include src="/api/user-info" />
<esi:include src="/api/ads" />
  • CDN 可以缓存页面的静态部分,动态部分单独请求源站
  • 减少回源请求量
  • 支持度有限:Cloudflare Workers、Akamai、Fastly 等支持,但实现各异

典型配置示例

# 带哈希的静态资源:浏览器 1 年,CDN 1 年
Cache-Control: public, max-age=31536000, immutable

# HTML 入口:浏览器每次验证,CDN 缓存 5 分钟
Cache-Control: no-cache, s-maxage=300, public

# API 响应:浏览器 1 分钟,CDN 不缓存
Cache-Control: private, max-age=60

# 图片:浏览器 1 天,CDN 7 天
Cache-Control: public, max-age=86400, s-maxage=604800

常见误解

  • “CDN 和浏览器用同一套缓存规则” → 不一定,s-maxageCDN-Cache-Control 可以给 CDN 独立的 TTL
  • “设置了 private CDN 就不会缓存” → 正确,这是 private 的核心语义
  • “CDN 缓存了就一定快” → 如果缓存过期回源,可能比不用 CDN 还慢(多了 CDN 一跳)
  • “purge 了就一定生效” → 边缘节点可能有延迟,部分节点可能还没收到 purge 指令
创建于 2026/5/23 更新于 2026/5/27