直接将完整User-Agent塞入缓存键会导致缓存爆炸,应先用map归一化为稳定设备类型(如ios、android、desktop),再同步配置proxy_cache_key含$device_type与add_header Vary "Device-Type" always,最后通过curl和日志验证分类与命中效果。

直接把完整的 User-Agent 字符串塞进缓存键,会导致缓存爆炸——同一页面因浏览器微小差异(如 Chrome 120 vs 121、iOS 17.4 vs 17.5)生成大量重复缓存项,命中率急剧下降。真正有效的做法是:先归一化 UA,再用稳定类别参与缓存键构建。
用 map 提前归类设备类型
在 http 块顶层定义映射,避免每个请求重复正则匹配:
- 覆盖主流标识:
~*android、~*iphone、~*ipad、~*mobile等,注意ipad必须显式列出,否则常被误判为桌面端 - 兜底明确:
default "desktop",不写空值或模糊值(如"pc"或"other"),防止后续逻辑异常 - 变量命名统一,例如
$device_type,后续所有地方(缓存键、Vary、路由)都复用它
缓存键必须包含设备类型且精简
静态资源(JS/CSS/图片)通常无需按设备区分;但 SSR 页面、响应式 HTML 或带设备专属逻辑的接口,就必须让缓存感知差异:
- proxy_cache_key 示例:
"$scheme$request_method$host$request_uri$http_origin$device_type" - 务必剔除干扰项:不用
$args(除非业务强依赖参数),不加$http_cookie或$http_referer - 若还依赖 Origin 头做跨域控制,则
$http_origin必须同时出现在proxy_cache_key和Vary中,否则隔离失效
Vary 响应头要同步且强制生效
仅改缓存键不够,客户端和 CDN 边缘节点靠 Vary 决定是否复用缓存:
- 配置:
add_header Vary "Device-Type" always;——always很关键,确保 304、404 等非 200 响应也携带该头 - 不能只写
Vary: User-Agent:UA 字符串太长太碎,边缘节点无法有效分层;而Device-Type是有限、稳定、语义清晰的值 - 若后端本身已返回
Vary,可用proxy_hide_header Vary清除,再由 Nginx 统一注入
上线前必须验证分类与命中效果
别只信配置,要用真实请求确认行为是否符合预期:
- 用 curl 检查响应头:
curl -H "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X)" -I https://example.com/page.html,确认返回Vary: Device-Type - 查看日志中
$upstream_http_x_cache(需后端配合输出)或自定义$sent_http_x_cache,识别HIT/MIS状态 - 对比不同 UA 请求同一 URL 的缓存路径(如通过
proxy_cache_path目录结构),确认是否生成了独立子目录


















