Vary 与缓存键

Vary 头部如何影响缓存键的构成,以及它对 CDN 命中率和缓存调试的影响。

#tech / dev / frontend #type / concept #status / evergreen #platform / browser #tech / network

[!info] related notes

Vary 与缓存键

一句话定义

Vary 告诉缓存:选择这个响应时用到了哪些请求头,缓存应该把那些请求头也纳入缓存键,否则不同客户端可能拿到错误的缓存版本。

缓存键的构成

默认情况下,缓存用 URL 作为主键。但同一 URL 可能因为请求头不同而需要不同的响应(比如压缩格式、语言)。Vary 把额外的请求头加入缓存键:

缓存键 = URL + Vary 指定的请求头的值

示例

Vary: Accept-Encoding

浏览器请求 /app.js 时:

  • Accept-Encoding: gzip → 缓存 gzip 版本,键 = /app.js + gzip
  • Accept-Encoding: br → 缓存 brotli 版本,键 = /app.js + br
  • Accept-Encoding: identity → 缓存未压缩版本,键 = /app.js + identity

三个版本独立存储,互不干扰。

常见 Vary 模式

Vary: Accept-Encoding(最常见)

Vary: Accept-Encoding

几乎所有压缩响应都应该设置。如果省略,缓存可能把 gzip 版本发给只支持 brotli 的客户端。

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 头部,但实际行为有差异:

CDNVary 支持程度
Cloudflare支持部分 Vary 值,可配置
Fastly完整支持,但需显式声明
Akamai有限支持,部分 Vary 值被忽略
AWS CloudFront默认忽略 Vary(可配置)

碎片化问题

Vary 值越多,缓存版本越多,命中率越低:

Vary: Accept-Encoding, Accept-Language, Cookie
→ 每个 URL 可能有 N(编码) × M(语言) × K(用户) 个缓存条目
→ 实际命中率趋近于 0

最佳实践:只在必要时使用 Vary,且只 Vary 真正影响响应内容的头部。

DevTools 调试

检查 Vary 头部

  1. 打开 Network 面板
  2. 点击请求,查看 Response Headers
  3. 找到 Vary 字段

常见问题

请求看起来被缓存了但返回了错误内容

  • 检查响应的 Vary 头部
  • 如果没有 Vary 或 Vary 值不完整,缓存可能在用错误的版本服务请求

CDN 命中率低

  • 检查是否有不必要的 Vary: CookieVary: Accept-Language
  • 检查 Vary 值是否组合过多

Vary 不匹配的表现

当缓存发现新请求的 Vary 头部值与已缓存的不匹配时:

  • 浏览器缓存:视为缓存未命中,发起新请求
  • CDN:可能返回 X-Cache: MISS 或类似标记

常见误解

  • Vary 控制浏览器行为” → 它控制的是缓存(浏览器和 CDN 都算)的行为
  • Vary: Cookie 很常见所以没问题” → 技术上没错,但会严重降低 CDN 命中率
  • “不设 Vary 就没事” → 如果响应确实依赖某个请求头但没设 Vary,缓存会返回错误的版本
  • Vary: * 等于 no-store” → 不完全一样,Vary: * 仍然允许浏览器本地缓存,只是不能用于匹配其他请求
创建于 2026/5/23 更新于 2026/5/27