有个同事遇到过一个怪 bug:用户反馈「手机端看到了电脑版页面」。排查半天,发现是 CDN 缓存串了——CDN 缓存了 PC 端的响应,手机端请求同一个 URL 时,CDN 直接返回了 PC 版。
这个问题的根源是:同一个 URL,服务器可能根据请求头返回不同内容(手机返回手机版、电脑返回 PC 版、不同语言返回不同语言版本)。缓存如果只按 URL 存,就会把 PC 版错给手机用户。Vary 头就是解决这个问题的。
缓存键:缓存按什么区分
先理解「缓存键」的概念。缓存存一个响应时,要有个 key,下次用同样的 key 能取出来。最朴素的缓存键就是 URL——/index.html 对应一个缓存项。
但 URL 相同不代表内容相同。同一个 /index.html,服务器可能根据 User-Agent 返回 PC 版或手机版。如果缓存只按 URL 存,PC 版和手机版会互相覆盖,谁先到缓存谁占住,后来的人拿到错的版本。
Vary 头的作用就是扩展缓存键。响应头 Vary: User-Agent 告诉缓存:「这个响应的内容依赖 User-Agent,缓存它时要把 User-Agent 也算进缓存键」。这样 PC 和手机的响应虽然 URL 相同,但因为 User-Agent 不同,缓存键不同,各存各的,不会串。
Vary 怎么工作
Vary 的值是一组请求头的名字,表示「这些请求头的值会影响响应内容」。缓存计算缓存键时,把这些头的值拼进去。
一个典型的场景:
响应头: Vary: Accept-Encoding, User-Agent
这告诉缓存:这个响应同时依赖 Accept-Encoding(压缩方式)和 User-Agent(设备类型)。缓存键就变成 URL + Accept-Encoding值 + User-Agent值 的组合。
- 手机 + gzip → 缓存项 A
- 手机 + br → 缓存项 B
- PC + gzip → 缓存项 C
- PC + br → 缓存项 D
四个组合各存各的,互不干扰。
最常见的 Vary 用法
实战中最常见的 Vary 配置:
Vary: Accept-Encoding:服务器根据客户端支持的压缩算法返回 gzip 或 br 版本。这是几乎每个站点都该配的——不配会导致「支持 br 的客户端拿到 gzip 版本」或更糟的串内容。Vary: User-Agent:响应内容随设备变化(响应式或独立移动版)。但 User-Agent 的值非常多样,可能导致缓存命中率下降(每个不同的 UA 都是一个独立缓存项),所以有的服务会做 UA 归一化(把「Chrome 120」和「Chrome 121」归成一类)。Vary: Accept-Language:响应内容随语言变化(i18n 国际化)。Vary: Origin:CORS 场景,响应的Access-Control-Allow-Origin随请求的 Origin 变化。
漏配 Vary 的坑
漏配 Vary 是真实的高频 bug,开头那个手机看到 PC 版就是典型。再举一个:服务端返回 JSON,支持 gzip 和 br 两种压缩,但没配 Vary: Accept-Encoding。结果 CDN 缓存了 gzip 版,一个只支持 br 的老客户端请求时,CDN 返回了 gzip 版给它——客户端解压失败,接口报错。
这种 bug 的特点是很隐蔽:本地测试没问题(本地不经过 CDN),一到生产环境偶发,排查困难。根因都是缓存键没包含影响内容的维度。
配 Vary 的原则很简单:响应内容依赖哪个请求头,Vary 就必须列哪个。漏配就串内容,多配(列了不影响的头)只会降低缓存命中率,不会出错。所以有疑问时宁可多配。
Vary: * 和反向情况
有两个特殊情况:
Vary: *:表示「这个响应每次都可能不同,别缓存」。等于禁用缓存,比no-cache更彻底。极少用。- 响应内容其实不随任何头变化:这时候不用配 Vary(或者不配也行)。但实践中很多服务器为了安全会统一配
Vary: Accept-Encoding,因为压缩几乎总是会影响内容。
取舍与边界
这个串内容的 bug 有个特别隐蔽的特征——本地永远复现不了:
Vary 有几个值得注意的边界:
- Vary 列的头越多,缓存命中率越低。因为每个组合都是一个独立缓存项,组合爆炸会让缓存变得稀疏。所以 Vary 要精确——只列真正影响内容的头。
- HTTP/2 里 Vary 依然有效,但头部压缩(HPACK)让多个请求头的存储更高效,缓解了 Vary 的开销。
- CDN 的 Vary 处理能力不同。有些 CDN 只认有限的几个 Vary 头(如只认 Accept-Encoding),自定义 Vary 可能被忽略。配 Vary 前要确认你的 CDN 支持哪些。
收束:Vary 是缓存正确性的补丁
强缓存和协商缓存解决「能不能用、要不要重传」,Vary 解决「缓存的是不是我要的那个版本」。三者合起来,才是完整的缓存正确性保障。漏了 Vary,缓存得越快,错得越离谱——手机用户飞快地看到 PC 版页面,比慢一点但正确更糟。
下一篇讲缓存层级——从浏览器到 CDN 到源站,缓存分布在不同位置,每一层都有自己的判断逻辑。
关于十三Tech
我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。
我相信 AI 是程序员的最佳搭档,也希望帮助每一位开发者更好地驾驭 AI。
如果你想继续跟完这套「图解 HTTP」,欢迎关注公众号 「十三Tech」。后续会按 URL 与报文、连接与传输、缓存与协商、安全与边界、HTTP/2 与 HTTP/3 这条线更新。

