HTTP 缓存与 CDN 加速实践:把可缓存性写成响应头里的策略
HTTP 缓存与 CDN 加速的关键不在开启 CDN,而在于给每类资源写清可缓存性、复用条件与失效方式。本文对照 Cache-Control 主要指令、浏览器与共享缓存的差异、按资源分层的响应头组合、常见失效场景与上线顺序,并说明 st 系列扩展与厂商默认行为需另行核实。
想让 CDN 真正提速,先要回答一个更基础的问题:这份响应允许谁缓存、缓存多久、什么时候必须回来问源站。把答案写成明确的响应头,浏览器和 CDN 才会按同一套规则复用内容;没写清楚时,CDN 往往只能按默认规则处理或频繁回源,加速效果和正确性都会打折。
先区分两种缓存#
浏览器缓存是“私有缓存”,只服务一个用户;CDN 属于“共享缓存”,一份副本要发给很多用户。HTTP 缓存规范里的指令正是按这条界线设计的,误用的代价通常不是慢,而是把 A 用户的内容发给 B 用户。
| 指令 | 含义 | 主要影响 |
|---|---|---|
max-age=N | 响应生成后 N 秒内可复用 | 私有与共享缓存都适用 |
s-maxage=N | 只针对共享缓存,并覆盖 max-age | CDN 的过期时间 |
no-cache | 可以存,但每次复用前必须验证 | 不等于“不缓存” |
no-store | 不存储 | 用于敏感响应 |
private | 只允许私有缓存存储 | CDN 不保存 |
must-revalidate | 过期后必须验证,不得用过期副本 | 牺牲可用性换一致性 |
最常见的误解是把 no-cache 当成“别缓存”。它的实际含义是“先验证再使用”,配合 ETag 时,命中验证会返回 304,省下的是响应体而不是往返。真正不想留痕的响应才用 no-store。
验证与缓存键:命中率的两根杠杆#
验证靠 ETag/If-None-Match,或更粗粒度的 Last-Modified/If-Modified-Since。CDN 命中时通常会带上 Age 头,表示这份副本已经在缓存里放了多久,可用于判断是边缘命中还是回源。
缓存键决定“什么算同一个资源”,Vary 是它的显式扩展。给 HTML 加 Vary: User-Agent 是常见的反面案例:UA 组合极多,副本被打散,命中率下降。按 HTTP 缓存规范,携带 Authorization 的请求,其响应默认不得被共享缓存复用,除非响应显式声明 public、s-maxage 或 must-revalidate。这条规则经常被忽略,也是鉴权接口串数据的来源之一。
按资源分层写策略#
不要给整站设同一个 max-age。更稳妥的做法是按“URL 是否会随内容变化”分层:
- 带内容指纹的静态资源(如
app.3f2a1b.js):Cache-Control: public, max-age=31536000, immutable。内容变了 URL 也变,长缓存没有失效风险。 - HTML 入口:
Cache-Control: no-cache加ETag。每次都验证,未变更时回 304,保证用户拿到的是新资源引用。 - 可公开的 JSON 接口:用
s-maxage让 CDN 吸收突发流量,秒数由业务能接受的数据陈旧窗口决定,而不是照抄别人的配置。 - 个性化或鉴权接口:
private,必要时no-store,并检查响应里有没有被一起缓存的Set-Cookie。
一个响应头分层的示例#
下面的配置仅用于说明分层思路,未执行,也未在具体 CDN 或 Nginx 版本上验证:
# 示例,未执行;add_header 的继承规则需按实际配置核对
location ~* \.(js|css|woff2)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
location = /index.html {
add_header Cache-Control "no-cache";
etag on;
}
观察是否生效可以看响应头,同样属于未执行的示例命令:curl -I https://example.com/index.html,重点看 Cache-Control、ETag、Age 三项。
上线顺序与失效路径#
- 先发布新指纹的静态资源,确认可访问;
- 再更新 HTML,让它引用新文件名;
- 需要主动失效时,只清入口 HTML,而不是清整站长缓存资源。
反过来做,用户会在短时间内拿到“新 HTML + 旧资源”或“旧 HTML + 新资源”的组合。另外,stale-while-revalidate 与 stale-if-error 属于扩展指令,源站过期后是否仍能返回旧副本,取决于缓存实现,不能当作通用保证。
还需要核实什么#
本文依据的是 HTTP 缓存语义中的通用定义,未附厂商文档,也没有联网核验。具体 CDN 的默认缓存规则、清除粒度与耗时、是否支持上述扩展指令、以及 immutable 在边缘节点的处理方式,都随厂商与套餐不同,待核实,上线前应逐条对照所用 CDN 的文档与自己的响应头实际输出。