Nginx缓存键需显式包含请求参数以实现多维度隔离,推荐用$arg_*提取关键参数并标准化顺序、编码和过滤无关参数,避免$args导致的顺序敏感与编码歧义问题。

在 Nginx 中,缓存键(cache_key)默认不包含请求参数,导致带不同 query 参数的请求可能命中同一缓存,造成内容错乱。要实现多维度隔离(比如 /api/user?id=1 和 /api/user?id=2 不共用缓存),关键是在 proxy_cache_key 中显式引入请求参数,并确保参数顺序、编码、过滤等行为可控。
使用 $args 构建基础缓存键
$args 是 Nginx 内置变量,代表原始未解码的完整 query string(如 id=1&name=test)。它简单直接,适合参数语义明确且无需排序的场景:
proxy_cache_key "$scheme$request_method$host$request_uri$args";
⚠️ 注意:$args 保留参数原始顺序和编码(如空格为 +,中文为 %E4%BD%A0),若客户端传参顺序不固定(如 ?b=1&a=2 vs ?a=2&b=1),会导致缓存键不同、重复缓存。
按字典序标准化参数(推荐)
为避免顺序敏感问题,应先对参数归一化。Nginx 本身不支持排序,但可通过 map + 自定义模块或 Lua 实现;更通用的做法是使用 nginx-module-vts 或 OpenResty 的 ngx.var.args 配合 Lua 处理。若仅用原生 Nginx,可借助 perl_set(需编译支持)或接受一定妥协:
- 强制要求上游或客户端统一参数顺序(开发约定)
- 只缓存无参或单参数接口(如
?id=xxx),用正则提取关键参数 - 用
$arg_id、$arg_token等独立变量拼接,忽略无关参数
按业务维度选择性纳入参数
并非所有参数都影响响应内容。例如分页接口中 page 和 size 必须参与缓存键,而 utm_source 或 debug=1 应排除:
set $cache_args "";
if ($arg_page != "") { set $cache_args "${cache_args}page=$arg_page&"; }
if ($arg_size != "") { set $cache_args "${cache_args}size=$arg_size&"; }
# 去掉末尾 &,再参与 key
proxy_cache_key "$scheme$request_method$host$request_uri?$cache_args";? 更健壮的方式是用 map 提前构造干净参数串:
map $args $cacheable_args {
default "";
~*^(.*&)?page=(?P<p>[^&]+)(&.*|)$ "page=$p";
~*^(.*&)?size=(?P<s>[^&]+)(&.*|)$ "size=$s";
~*^page=(?P<p2>[^&]+)&size=(?P<s2>[^&]+)$ "page=$p2&size=$s2";
}
proxy_cache_key "$scheme$request_method$host$request_uri?$cacheable_args";处理参数编码与大小写敏感
Nginx 变量如 $arg_xxx 自动解码 URL 编码(%20 → 空格),但 $args 保持原始编码。若需统一,建议:
- 优先使用
$arg_*提取关键参数,避免$args的编码歧义 - 对参数值做小写转换(如
$arg_user_id可能大小写混用):用map+ 正则捕获后转小写(需 Nginx ≥ 1.11.8) - 截断过长参数值防止缓存键膨胀:
substr($arg_q, 0, 100)(需 Lua)或用map配合正则限制长度
不复杂但容易忽略的是:修改 proxy_cache_key 后必须同步检查 proxy_cache_bypass 和 proxy_no_cache 是否仍匹配预期逻辑,否则可能绕过缓存或误缓存。实际部署前建议用 log_format 打印生成的 key 值验证效果。


















