应归一化关键参数以统一缓存key:用map提取并排序id/lang等必要参数,过滤utm等干扰项;通过正则i标志忽略大小写,跳过nonce/timestamp等动态参数;添加X-Cache-Key响应头和日志验证key一致性。

直接用 $request_uri 作缓存 key,会导致 /api?lang=zh&id=123 和 /api?id=123&lang=zh 被当成两个不同请求——参数语义相同,但顺序、大小写或带了 utm 等干扰项,缓存就白白浪费。关键不是“能不能缓”,而是“怎么让不同写法归成同一个 key”。
提取并归一化关键参数
Nginx 不解析 query string,但可用 map 提前提取、排序、过滤。只保留真正影响响应结果的参数(如 id、lang、category),丢掉 utm_*、ref、ts 等跟踪参数。
示例配置:
map $args $cache_args {
~^(.*&)?(id=[^&]+)(?:&|$)(.*&)?(lang=[^&]+)(?:&|$).* "&";
~^(.*&)?(id=[^&]+)(?:&|$).* "";
~^(.*&)?(lang=[^&]+)(?:&|$).* "";
default "";
}
再在 location 中设置:
proxy_cache_key "$host$uri?$cache_args";
统一大小写与空格格式
客户端可能发来 ID=123 或 id=123,甚至 lang = zh(含空格)。Nginx 默认区分这些差异。解决办法不是改请求,而是在构造 key 时做标准化处理:
- 用
map配合正则捕获时,加上i标志实现不区分大小写匹配 - 用
rewrite预处理时,可先用set $clean_args ...拼接,并用lower()类逻辑(需配合 Lua 模块);更轻量的做法是后端统一转小写,Nginx 只负责按约定提取 - 避免在 key 中直接使用未清洗的
$args,哪怕加了try_files或rewrite,也要确保最终 key 是确定的
跳过含动态参数的请求
有些参数天生不该进缓存,比如 nonce=xxx、timestamp=1718613081。与其让它污染 key,不如提前绕过:
- 用
if ($args ~ "nonce=|timestamp=") { set $skip_cache "1"; } - 搭配
proxy_cache_bypass $skip_cache;和proxy_no_cache $skip_cache; - 这样请求仍会转发给后端,但不会写入缓存,也不会读取旧缓存
验证与调试不能少
光配对没用,得确认 key 真的一致:
- 加响应头:
add_header X-Cache-Key "$host$uri?$cache_args" always;(仅调试期启用) - 查日志:在
log_format中加入$upstream_cache_status $host$uri?$cache_args,对比命中/未命中请求的 key 是否真相同 - 观察
X-Cache-Status:值为HIT才算成功,MISSED或BYPASS就要回溯 key 构造逻辑


















