nginx默认以完整$request_uri为缓存key导致语义相同但参数顺序/大小写/无关参数不同的请求被误判为不同资源;应通过map归一化关键参数、过滤utm等干扰项、统一小写与空格,并配合proxy_cache_bypass/no_cache跳过动态参数,最后用x-cache-status和日志验证key一致性。

处理带复杂查询字符串的请求时,Nginx 默认缓存行为往往不理想——它把整个 $request_uri(含参数顺序、空格、重复键、无关参数)作为缓存 key,导致语义相同但参数排列不同的请求被当成不同资源缓存,浪费空间且降低命中率。
统一查询参数顺序与过滤无关参数
Nginx 本身不解析 query string,但可通过重写 + 变量拼接构造标准化 key。核心思路是:先用 map 或 rewrite 提前归一化参数,再在 proxy_cache_key 中使用稳定字段组合。
- 避免直接用
$request_uri,改用$host$uri$is_args$args基础结构,但需进一步控制$args - 对必须参与缓存判定的参数(如
id、lang),提取为独立变量;对干扰项(如utm_*、ref、ts)在进入缓存逻辑前清除 - 示例:用
map提取关键参数并丢弃跟踪参数map $args $cache_args { ~^(.*&)?(id=[^&]+)(?:&|$)(.*&)?(lang=[^&]+)(?:&|$).* "$2&$4"; ~^(.*&)?(id=[^&]+)(?:&|$).* "$2"; ~^(.*&)?(lang=[^&]+)(?:&|$).* "$2"; default ""; }然后设置:proxy_cache_key "$host$uri?$cache_args";
忽略大小写与空格差异
部分客户端或 SDK 会生成带空格、大小写混用的参数名(如 ID=123 和 id=123),Nginx 默认区分。解决方式不是修改原始请求,而是构造 key 时统一转换:
- 用
map配合正则捕获组 + 小写化变量(需 Nginx ≥ 1.11.8,支持$arg_NAME自动小写解析) - 更稳妥做法:在应用层或 upstream 做参数标准化,Nginx 层只信任已清洗过的变量
- 若必须在 Nginx 处理,可借助 Lua 模块(如
ngx_http_lua_module)对$args执行排序+小写+去重,再注入 key
跳过动态参数的缓存判定
有些参数仅用于埋点或防刷(如 _t=1717652033、sign=abc),不应影响缓存逻辑。除了用 map 过滤,还可结合 proxy_cache_bypass 和 proxy_no_cache 实现条件绕过:
-
proxy_cache_bypass $arg_sign $arg_t;:只要存在sign或t参数,就跳过缓存读取(仍可能写入) -
proxy_no_cache $arg_sign $arg_t;:存在这些参数时,完全不写入缓存 - 二者配合可实现「读不缓存、写也不缓存」,避免污染 cache key 空间
验证与调试缓存行为
实际部署后,务必确认 key 是否按预期生成。最有效方式是添加响应头暴露缓存状态和 key:
- 启用状态头:
add_header X-Cache-Status $upstream_cache_status; - 记录实际 key(仅调试用,勿上线):
log_format cache_log '$remote_addr - $upstream_cache_status "$host" "$request_uri" "$cache_args"'; - 用
curl -I多次请求不同参数组合,比对X-Cache-Status是HIT还是MISS,快速定位 key 分裂问题











