HTTP 缓存到底该由谁决定:强缓存、协商缓存与 Vary 的分工

举报
yd_238022924 发表于 2026/10/30 17:24:15 2026/10/30
【摘要】 从强缓存与协商缓存的分工讲起,梳理 Cache-Control 常用指令的误区、ETag 与压缩的暗坑、Vary 的必要性,并给出一套能直接落地的默认策略。

HTTP 缓存到底该由谁决定:强缓存、协商缓存与 Vary 的分工

很多团队都遇到过这样的场景:后端把接口返回值改了,前端刷新页面还是老数据;或者反过来,静态资源明明发了新版本,用户那边仍旧加载旧的 JS,最后只能让人「清一下缓存试试」。这类问题八成不是浏览器的锅,而是响应头里那几句话没写清楚。HTTP 缓存本质上是一套协商机制,写对了能省掉大量带宽和延迟,写错了就是一次次线上事故。

先分清两类缓存

强缓存(fresh cache)指的是浏览器在有效期内根本不发请求,直接从本地磁盘或内存里取。决定它的是 Cache-Control 的 max-age,以及过时的 Expires。它的优点是快,缺点是完全绕过服务端,一旦内容需要更新,只能靠改文件名或 URL 里的版本号。

协商缓存(validation)指的是浏览器带着凭证去问服务端:「我这个副本还有效吗?」有效就返回 304 Not Modified,不传响应体;无效才返回 200 和新内容。凭证有两种:Last-Modified / If-Modified-Since 用时间戳,ETag / If-None-Match 用内容摘要。时间戳精度只到秒,同一秒内改两次就识别不出来,所以现代服务端优先用 ETag。

两者不是互斥的:一个响应可以既带 max-age 又带 ETag。有效期内走强缓存,过期后自动降级为协商缓存,这才是常见的最优组合。

几个最容易写错的指令

no-cache 这个名字极具误导性。它的意思不是「不缓存」,而是「可以缓存,但每次使用前必须向服务端确认」。真正禁止存储的是 no-store,通常只用于含敏感信息的响应,比如登录后的个人资料、一次性验证结果。

private 和 public 管的是谁能存。带用户身份的内容必须标 private,否则中间层可能把 A 用户的响应缓存下来发给 B 用户,这是最危险的一类缓存事故。反过来,真正对所有用户一致的资源可以标 public,让 CDN 放心缓存。

s-maxage 只对共享缓存(CDN、反向代理)生效,并且会覆盖 max-age。它非常适合「边缘节点缓存久一点、浏览器缓存短一点」的策略。

stale-while-revalidate 是个很实用的补充:允许在过期后的一小段时间内先返回旧内容,同时在后台异步刷新。对首屏体验的提升很明显,代价是用户可能看到略旧的数据。

ETag 与压缩的暗坑

ETag 常见两种生成方式:基于内容哈希的强 ETag,和基于文件元信息(大小、修改时间)的弱 ETag(W/ 前缀)。弱 ETag 语义上表示「内容等价即可」,适合做协商,不适合做字节级校验。

一个很隐蔽的问题是:如果 ETag 在压缩之前生成,而中间层对不同客户端做了不同的压缩(比如有的支持 gzip、有的支持 brotli),同一个 URL 就会产生多个不同的实体,却共用一个 ETag。客户端拿着 A 版本的 ETag 去请求,服务端返回 304,结果拿到的是 B 版本的解压内容,校验就失效了。解决办法是让 ETag 参与压缩相关的 Vary,或者干脆用内容哈希并保证压缩前后一致。

Vary 不是可选项

Vary 告诉缓存「这个响应的正确性依赖哪些请求头」。只按 URL 做缓存的 CDN,遇到 Accept-Encoding、Accept-Language、User-Agent 有差异的场景就会串味。最常见的翻车是响应式图片:同一个地址按 User-Agent 返回不同尺寸,但没写 Vary: User-Agent,于是移动端拿到了桌面端的大图。

需要注意的是,Vary 的维度越多,缓存命中率越低。Vary: * 等于告诉所有缓存「别存了」。所以它应该只列真正影响内容的头。

一套能落地的默认策略

静态资源(带内容哈希的文件名)可以放心长缓存:Cache-Control: public, max-age=31536000, immutable,因为文件名变了 URL 就变了,不需要协商。

HTML 入口文件相反,应该短缓存甚至不缓存:Cache-Control: no-cache,让浏览器每次都回来确认,这样发版才能立刻生效。

接口按数据特性分档:完全公开且变化慢的(配置、字典)给几分钟 max-age;带用户身份的必须 private, no-store 或至少 private, max-age=0, must-revalidate;实时性要求高的直接 no-store。

最后一条经验:缓存策略要写进接口文档,而不是散落在各个服务的代码里。因为一旦前后端、网关、CDN 三处各写一套,排查问题时就没人说得清到底是谁在缓存。发版流程里加一步「检查关键响应的缓存头」,比事后让用户清缓存便宜得多。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。