HTTP 缓存配不对,省下的流量会变成客诉:强缓存与协商缓存的实用分工
页面明明没改,用户却看到旧内容;或者每次刷新都重新下载,带宽账单居高不下。这两种问题常常同时出现,根源都在缓存策略没有分工:该长期缓存的没缓存,该及时更新的又被缓存住了。本文把 HTTP 缓存拆成两层,讲清楚各自管什么、怎么配、以及容易踩的坑。
强缓存:不发请求,直接用本地副本
强缓存由 Cache-Control 控制。浏览器拿到响应后,在 max-age 有效期内再次请求同一资源时,根本不会发网络请求,直接用本地副本,速度最快、也最省流量。
强缓存只适合内容不变的资源,典型做法是给静态资源文件名加上内容哈希,例如 app.a3f9c2.js。文件内容一变,哈希就变,URL 也跟着变,浏览器自然会去下载新文件,旧缓存永远不会挡住新版本。反过来,没有哈希的入口文件 index.html 绝不能设长缓存,否则用户可能长期停留在旧入口,引用的还是旧哈希资源。
一个常见的错误是给所有响应统一加一个很大的 max-age。API 接口、HTML 页面、用户相关的 JSON 都不适合强缓存,一旦缓存住,看到的就是过期数据。强缓存的适用面其实很窄:带哈希的 JS、CSS、图片、字体这四类,配 Cache-Control: public, max-age=31536000, immutable 就够了。
协商缓存:发请求,但不一定传 body
协商缓存解决的是“资源可能变了,但大概率没变”的场景。服务器在响应里带上 ETag 或 Last-Modified,浏览器下次请求时带上 If-None-Match 或 If-Modified-Since,服务器比对后如果没变,只回 304 Not Modified,不传 body,省下传输量,但仍然要花一次往返。
ETag 优先于 Last-Modified。Last-Modified 只有秒级精度,一秒内多次修改发现不了;而且它只看修改时间,文件被反复写入相同内容也会误判为已变。ETag 是内容摘要,内容不变就不变,精度更高。Nginx 默认会给静态文件生成 ETag,动态接口则需要应用自己算,算不出来时宁可不发,也不要发一个固定不变的 ETag。
协商缓存适合 HTML、API 响应这类“经常看、偶尔变”的资源:设置 Cache-Control: no-cache,意思是每次都发请求验证,但可以用 304 省流量。注意 no-cache 不是“不缓存”,它是不用过期副本、每次验证;真正禁止缓存的是 no-store,登录态、支付结果、个人数据才用它。
两层配合:入口不缓存,静态长缓存
一套不容易出错的分工是这样的。入口 HTML 用 no-cache 加 ETag,每次验证,保证用户总能拿到最新入口;带哈希的静态资源用一年强缓存,改动靠新 URL 生效;API 接口默认 no-store 或 no-cache,绝不给 max-age;图片等多媒体如果 URL 不带哈希,就用较短的 max-age 加协商缓存兜底。
上线后验证缓存是否生效,不用猜。打开浏览器开发者工具的网络面板,看 Size 列:显示为 disk cache 或 memory cache 的是强缓存命中,状态码 304 的是协商缓存命中,200 且有流量的是真的重新下载。三个状态各出现一次,基本说明策略在按预期工作。
容易踩的几个坑
第一个坑是 CDN 回源配置覆盖了源站的 Cache-Control。源站配得再好,CDN 节点如果强制缓存或忽略源站头,策略就全乱了。改缓存策略时,源站和 CDN 要一起改,改完还要清掉 CDN 上已有的旧缓存。
第二个坑是 Vary 头缺失。接口根据 Accept-Encoding 或 Authorization 返回不同内容时,如果响应没有 Vary,缓存可能把 gzip 版本发给不支持的客户端,或者把 A 用户的数据发给 B 用户。动态内容开缓存之前,先确认 Vary 是否写对。
第三个坑是缓存了带 Set-Cookie 的响应。登录接口一旦被中间缓存住,后续用户可能拿到别人的会话。凡是响应里有 Set-Cookie 的,默认就不要让共享缓存存它,Cache-Control 里加上 private 是最基本的防护。
缓存策略没有银弹,但分工清楚之后,大部分问题都能定位:看到旧内容,先看是强缓存还是协商缓存没验证;流量下不来,先看静态资源有没有走强缓存。把这两层各司其职,省下的流量和客诉都会少很多。
- 点赞
- 收藏
- 关注作者
评论(0)