HTTP 缓存策略 Cache-Control 与 ETag 实战:让接口又快又不出错
很多接口变慢,问题并不在数据库或代码逻辑,而在重复请求上:同一个静态资源、同一份字典数据、同一页列表,被浏览器和 CDN 一遍遍重新拉取。HTTP 缓存就是用来省掉这些重复劳动的,但用错了反而会让用户看到旧数据。本文用 Cache-Control 与 ETag 这一对组合,给出一套可直接落地的分层做法。
一、先分清两种缓存
强缓存由浏览器自己判断,不发请求。服务器在响应里写:
Cache-Control: public, max-age=3600
在 max-age 有效期内,浏览器直接用本地副本,速度最快,也最省带宽。
协商缓存则要发一次轻量请求问服务器:我手里的版本还新吗?服务器返回 304,说明没变,浏览器继续用本地副本;返回 200,则带回新内容。强缓存管“能不能不问”,协商缓存管“问了之后要不要重新下载”。
二、Cache-Control 的常用写法
public, max-age=31536000, immutable:适合带内容哈希文件名的 JS、CSS、图片,文件名一变就是新资源,旧缓存不会误伤。private, max-age=60:只允许浏览器缓存,不允许共享缓存存用户相关内容,适合个人化但变化不快的页面片段。no-cache:不是“不缓存”,而是每次使用前必须先向服务器验证,适合经常变但又想省下 304 之后传输量的接口。no-store:任何地方都不要存,适合订单、支付结果、含敏感信息的响应。
容易踩的坑是把 max-age 和 no-cache 混在一起写,却以为前者会生效;实际语义以后者为准,先验证再用。
三、ETag:给资源一个版本号
ETag 是服务器给某个版本资源贴的标识,内容一变标识就应变。浏览器下次请求时带上它:
If-None-Match: "v123"
服务器比对后若一致返回 304 且不带正文,只花一次往返;若不一致返回 200 与新内容。比 Last-Modified 更可靠,因为它不依赖秒级时间精度,也能区分同一秒内多次改动。弱 ETag 以 W/ 开头,表示语义相同但字节可能不同,适合经过压缩或格式转换仍可复用的场景。
四、实战分层建议
第一层是静态资源:构建时生成带哈希的文件名,配一年长缓存加 immutable,发布新版不用清缓存,旧页面也不会引用错文件。入口 HTML 本身不要长缓存,用 no-cache 加 ETag,改版后用户刷新即可拿到新入口。
第二层是读多写少的接口:字典、配置、公开榜单等,配 private, max-age=60 或 no-cache,并返回稳定的 ETag。内容不变时一次 304 就结束,带宽和后端压力都下来了。
第三层是个人数据与写操作结果:用 no-store,不要为了省一次请求让下一位用户或同一台共用设备看到别人的数据。POST、PUT、DELETE 本来就不应被当作可缓存的 GET 来处理。
五、上线前自查清单
- 用
curl -I看响应头,确认强缓存与协商缓存没有互相矛盾。 - 确认 CDN 与反向代理没有私自去掉 ETag,也没有把
Vary丢掉导致不同语言或压缩版本串味。 - 多节点部署时,ETag 生成方式要一致,否则同一资源在不同节点上来回 200,缓存等于失效。
- 改版后先看入口 HTML 是否 304、静态文件名是否真的变了,再看接口是否按预期返回 304。
缓存的目标不是把所有东西都存起来,而是把“不变的东西少传、常变的东西及时验”。先按资源变化频率分层,再用 Cache-Control 定有效期、用 ETag 做版本校验,接口自然会又快又稳。
- 点赞
- 收藏
- 关注作者
评论(0)