HTTP 缓存:Cache-Control 与 ETag 各管一半
Cache-Control 决定「要不要发请求」,ETag 决定「发出去之后能不能省下响应体」。把两者混谈是缓存问题的根源:改一个参数不会同时修好两侧。
缓存问题几乎都能拆成两个独立的问题:要不要发这个请求,和发了之后能不能省下响应体。前者由 Cache-Control 管,后者由 ETag / Last-Modified 管。
第一半:要不要发请求
Cache-Control: public, max-age=31536000, immutable
带哈希文件名的构建产物就该长这样:一年、immutable、直接不发请求。
Cache-Control: no-cache
no-cache 是最容易被误读的指令:它不是不缓存,而是「可以缓存,但每次必须回来验证」。真正禁止存储的是 no-store。
| 指令 | 浏览器存吗 | 会发请求吗 |
|---|---|---|
max-age=0 |
存 | 会,带验证 |
no-cache |
存 | 会,带验证 |
no-store |
不存 | 会,且不写盘 |
immutable |
存 | 有效期内部不发 |
第二半:能不能省下响应体
服务端返回 ETag: "abc123",浏览器下次带上:
If-None-Match: "abc123"
没变就回 304 Not Modified,没有响应体。省的是传输,不是往返 —— 请求还是发了。
etag on;
组合起来看
| 目标 | Cache-Control | 验证器 |
|---|---|---|
| 带哈希的静态资源 | max-age=31536000, immutable |
不需要 |
| HTML 页面 | no-cache |
ETag |
| API 可缓存响应 | max-age=60, must-revalidate |
ETag |
| 敏感数据 | no-store |
无意义 |
HTML 通常选 no-cache + ETag:内容变了立刻生效,没变就一个 304,省下整个文档。
两个容易踩的点
改了 ETag 的生成方式会让所有缓存失效一次。 如果 ETag 由文件 mtime 派生,重新部署(即使内容没变)也会让全部客户端重新下载。内容哈希更稳。
CDN 会改写或吃掉缓存头。 源站写 max-age=60,CDN 可能按自己的规则缓存更久。排查「改了没生效」先看 CDN 的缓存状态头(eo-cache-status、cf-cache-status 之类),别只盯源站。
Cache-Control 管要不要问,ETag 管问了之后给不给内容。混在一起谈就永远修不好。

评论
…