HTTP 缓存与 CDN 加速实践:缓存键、生命周期与失效路径怎么定
缓存配置要先定缓存键,再按资源类别给生命周期,最后设计失效与验证路径。文章区分了 no-cache 与 no-store、max-age 与 s-maxage 的语义差别,指出多节点部署下 ETag 一致性与 Vary 缺失是常见回源原因,并给出用 Age 头与源站日志核对命中率的判定口径;具体 CDN 厂商的默认 TTL、清除传播时间和扩展指令支持情况需查对应文档,本文未做实测。
- 缓存键 = URL(含 query)+ Vary 指定的请求头;键定义不清,后面所有 TTL 调整都是在猜。
- no-cache 允许存储但每次使用前必须再验证;no-store 才是不存储,两者不能互换。
- 优先用内容哈希 URL 做失效,purge 是兜底手段;purge 的生效时间依赖厂商,需实测而非假设。
把静态资源放到 CDN 上并不等于做了缓存加速。真正决定效果的是三件事:缓存键怎么定义、每类资源的生命周期怎么给、内容变了怎么让它失效。本文只讨论协议语义与工程判定口径,不提供任何命中率或加速比数字——那类数据必须在具体源站、具体节点分布和具体流量结构下测出来,本文未做此类测试。
先定缓存键,再谈 TTL#
缓存键是边缘节点判断“这个请求能不能复用已有副本”的依据。默认情况下它就是请求 URL,包含 query string,且参数顺序敏感。?a=1&b=2 与 ?b=2&a=1 在多数实现里是两个键。常见问题是页面把埋点参数、时间戳、会话随机数拼进静态资源 URL,键空间被撑大,复用率自然下降。
当同一 URL 需要按请求头返回不同内容时,用 Vary 声明参与键计算的请求头,最典型的是 Vary: Accept-Encoding。反过来,Vary: User-Agent 或 Vary: * 会让键近乎唯一,等价于关掉共享缓存。
CDN 厂商普遍支持自定义缓存键(忽略指定 query 参数、按 Cookie 或请求头分流),但各家的默认行为和可配置粒度不同,需查对应厂商文档核实,本文不逐一断言。
按资源类别给生命周期#
HTTP 缓存语义由 IETF 的 RFC 9111 定义(本次未提供原文链接,引用具体条款时请核对原文)。几个高频指令的边界值得先说清:max-age 约束所有缓存,含浏览器私有缓存;s-maxage 只约束共享缓存并覆盖 max-age;no-cache 是“可以存,但每次用之前必须再验证”,no-store 才是完全不存储,涉敏响应应以后者为准。
| 资源类别 | 建议头部组合 | 判定理由 |
|---|---|---|
| 带内容哈希的静态资源 | public, max-age=31536000, immutable | URL 随内容变化,可长期缓存;immutable 的客户端支持范围需自行核实 |
| HTML 入口页 | no-cache 配合 ETag | 需及时反映新版本引用,用再验证换低延迟 |
| 登录态接口响应 | private, no-store | 不得进入任何共享缓存 |
以上是配置方向的建议,不是实测结论。实际取值要在新鲜度与回源量之间权衡,权衡依据只能来自自家流量。
条件请求与再验证#
客户端带 If-None-Match(或 If-Modified-Since)发起再验证,源站无变化时返回 304,省掉响应体。多节点部署时一个容易忽略的点是:如果不同源站节点为同一份内容生成不同的 ETag,再验证会持续判定为“已变化”,缓存形同失效。这是基于 ETag 语义的推断,遇到反复回源时应优先排查。
HTTP/1.1 200 OK
Content-Type: application/javascript; charset=utf-8
Cache-Control: public, max-age=60, s-maxage=3600, stale-while-revalidate=86400
ETag: "a1b2c3d4"
Vary: Accept-Encoding
上面是示意的响应头片段,未在真实环境执行或验证;stale-while-revalidate 属于扩展指令,各 CDN 与客户端的支持情况待核实。
失效路径:优先版本化,purge 兜底#
- 版本化 URL:文件名带内容哈希,发布即新键,无需清除旧副本。这是最可控的路径。
- 短 TTL + 再验证:适用于入口页这类不能改名的资源。
- purge 接口:作为前两者失效时的补救。清除请求的生效时间、按 URL 还是按 tag 清除、是否有速率限制,均依赖厂商实现,需以文档与实测为准。
怎么核对配得对不对#
- 看响应头的
Age:值大于 0 说明命中了共享缓存,且能反推副本年龄。 - 看厂商提供的调试响应头:字段名各不相同,以文档为准。
- 比对边缘访问日志与源站日志:命中率的统计口径必须写清是请求数还是字节数、统计窗口多长、是否排除健康检查与预取流量。口径不同的两个数字不可直接比较。
结论可以缩到一句:缓存配置是可判定的工程问题——键定义得清、头写得对、失效路径有预案,剩下的是用日志验证,而不是靠调大 TTL 碰运气。