Vary 与缓存键
Vary 头部如何影响缓存键的构成,以及它对 CDN 命中率和缓存调试的影响。
#tech / dev / frontend
#type / concept
#status / evergreen
#platform / browser
#tech / network
[!info] related notes
- 所属 MOC: 浏览器缓存体系
- 前置概念: 强缓存, 协商缓存
- 关联概念: CDN 边缘缓存, DevTools 缓存调试
Vary 与缓存键
一句话定义
Vary 告诉缓存:选择这个响应时用到了哪些请求头,缓存应该把那些请求头也纳入缓存键,否则不同客户端可能拿到错误的缓存版本。
缓存键的构成
默认情况下,缓存用 URL 作为主键。但同一 URL 可能因为请求头不同而需要不同的响应(比如压缩格式、语言)。Vary 把额外的请求头加入缓存键:
缓存键 = URL + Vary 指定的请求头的值
示例
Vary: Accept-Encoding
浏览器请求 /app.js 时:
Accept-Encoding: gzip→ 缓存 gzip 版本,键 =/app.js+gzipAccept-Encoding: br→ 缓存 brotli 版本,键 =/app.js+brAccept-Encoding: identity→ 缓存未压缩版本,键 =/app.js+identity
三个版本独立存储,互不干扰。
常见 Vary 模式
Vary: Accept-Encoding(最常见)
Vary: Accept-Encoding
几乎所有压缩响应都应该设置。如果省略,缓存可能把 gzip 版本发给只支持 brotli 的客户端。
Vary: Cookie
Vary: Cookie
用于个性化内容(登录状态不同,响应不同)。代价很高:每个用户的 Cookie 不同,缓存命中率极低。通常只在确实需要时使用。
Vary: Accept-Language
Vary: Accept-Language
多语言网站使用。如果支持 20 种语言,同一个 URL 最多可能缓存 20 个版本。
多值组合
Vary: Accept-Encoding, Accept-Language
缓存键变成 URL + Accept-Encoding + Accept-Language,组合爆炸风险高。
Vary: *
Vary: *
表示这个响应不能被任何其他请求复用——相当于禁止共享缓存存储这个响应用于后续请求。RFC 9111 Section 5.5 定义。
通常出现在响应依赖了无法通过请求头表达的因素(比如请求体内容、时间等)。
对 CDN 的影响
CDN 必须尊重 Vary 头部,但实际行为有差异:
| CDN | Vary 支持程度 |
|---|---|
| Cloudflare | 支持部分 Vary 值,可配置 |
| Fastly | 完整支持,但需显式声明 |
| Akamai | 有限支持,部分 Vary 值被忽略 |
| AWS CloudFront | 默认忽略 Vary(可配置) |
碎片化问题
Vary 值越多,缓存版本越多,命中率越低:
Vary: Accept-Encoding, Accept-Language, Cookie
→ 每个 URL 可能有 N(编码) × M(语言) × K(用户) 个缓存条目
→ 实际命中率趋近于 0
最佳实践:只在必要时使用 Vary,且只 Vary 真正影响响应内容的头部。
DevTools 调试
检查 Vary 头部
- 打开 Network 面板
- 点击请求,查看 Response Headers
- 找到
Vary字段
常见问题
请求看起来被缓存了但返回了错误内容:
- 检查响应的
Vary头部 - 如果没有
Vary或 Vary 值不完整,缓存可能在用错误的版本服务请求
CDN 命中率低:
- 检查是否有不必要的
Vary: Cookie或Vary: Accept-Language - 检查
Vary值是否组合过多
Vary 不匹配的表现
当缓存发现新请求的 Vary 头部值与已缓存的不匹配时:
- 浏览器缓存:视为缓存未命中,发起新请求
- CDN:可能返回
X-Cache: MISS或类似标记
常见误解
- “
Vary控制浏览器行为” → 它控制的是缓存(浏览器和 CDN 都算)的行为 - “
Vary: Cookie很常见所以没问题” → 技术上没错,但会严重降低 CDN 命中率 - “不设
Vary就没事” → 如果响应确实依赖某个请求头但没设 Vary,缓存会返回错误的版本 - “
Vary: *等于no-store” → 不完全一样,Vary: *仍然允许浏览器本地缓存,只是不能用于匹配其他请求