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=1和app.js?v=2是不同缓存条目 - 忽略 query string:部分 CDN 可配置,
app.js?v=1和app.js?v=2视为同一资源 - 自定义 key:可用 CDN 配置添加/移除头部到缓存键
Vary 的影响
CDN 必须尊重 Vary 头部,但支持程度因 CDN 而异:
| CDN | Vary 支持 |
|---|---|
| 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-maxage、CDN-Cache-Control可以给 CDN 独立的 TTL - “设置了
privateCDN 就不会缓存” → 正确,这是private的核心语义 - “CDN 缓存了就一定快” → 如果缓存过期回源,可能比不用 CDN 还慢(多了 CDN 一跳)
- “purge 了就一定生效” → 边缘节点可能有延迟,部分节点可能还没收到 purge 指令