Vary 响应头是怎么把命中率吃掉的
Vary 告诉 CDN「这个响应随某个请求头变化」,于是同一个 URL 被拆成多份副本。一旦 Vary 了 User-Agent,副本数就接近无穷。
6 分钟缓存与命中

要点
Vary: User-Agent 会让每种浏览器版本各存一份副本,命中率几乎必然崩塌。真正需要区分的通常只有 Accept-Encoding。
Vary 做了什么
CDN 的缓存键默认是 URL。加上 Vary 之后,缓存键变成「URL + 指定请求头的值」——这意味着同一个地址会按那个请求头的取值分裂成多份独立副本。
Vary: Accept-Encoding
# 副本数 ≈ 压缩算法种类(br / gzip / 无),可控
Vary: User-Agent
# 副本数 ≈ UA 字符串的种类,几乎无上限Accept-Encoding 的取值就那么几种,分裂出三四份副本是可接受的代价,而且这个区分是必要的——不能把 Brotli 压缩过的响应发给不支持它的客户端。User-Agent 则完全不同:每个浏览器小版本、每个设备型号都是不同的字符串。
为什么 Vary: User-Agent 是致命的
现实中的 UA 字符串种类是成千上万的量级。这意味着一个原本可以被全网共享的资源,被切成了成千上万份互不相干的缓存。
最容易搞错的一点
很多框架和服务端组件会自动加上 Vary: User-Agent(常见于做设备适配的中间件),而开发者根本没意识到。命中率上不去时,先去看响应头里有没有这一行。
该怎么办
- 先用实测工具看响应头里实际有哪些 Vary,不要相信配置文件;
- 需要做设备适配时,优先在 CDN 侧用「设备类型」缓存键(多数厂商提供 mobile/desktop 两档),而不是 Vary 整个 UA;
- 需要多语言时,Vary: Accept-Language 前先把取值归一化到你实际支持的语种;
- 任何情况下都不要 Vary: Cookie——那等于给每个用户各存一份。
在你的网站验证
边缘核验看你的资源实际返回了哪些 Vary 与缓存响应头