Nginx的split_clients模块通过IP哈希实现静态资源灰度,核心是将$remote_addr(或真实IP)经MD5哈希取32位整数,归一化至[0,100)后按配置百分比区间匹配分组,再用rewrite重写路径(如/v2/),确保同一IP始终命中同一版本,3%配置即稳定分流约3%用户,零后端侵入。

用 Nginx 的 split_clients 模块做基于 IP 的静态资源灰度,核心是让一部分用户(按 IP 哈希固定分配)加载新版本静态文件,其余走老版本,不依赖后端、不改业务代码,适合 JS/CSS/图片等资源的渐进式发布。
原理:IP 哈希 + 预设比例 + 路径重写
split_clients 会把客户端真实 IP(需确保 $remote_addr 准确,前置有代理时要用 $http_x_forwarded_for 并配合 set_real_ip_from)做哈希,映射到 0–99 的整数,再根据配置的比例范围决定命中哪个变量值。这个值可用于重写静态资源请求路径,比如加前缀 /v2/ 或换域名。
- 哈希结果稳定:同一 IP 每次请求都分到同一组,保证灰度用户看到一致的新资源
- 比例可调:如
5%{ .v2; }表示约 5% 的 IP 走新路径,支持小流量验证 - 零后端侵入:所有逻辑在 Nginx 层完成,前端无需加版本号或 SDK
配置关键步骤(含真实可用片段)
以下配置假设你已通过 real_ip_module 正确获取真实 IP,并希望将 3% 用户导向 /static/v2/ 目录:
map $remote_addr $static_version {
default "";
~^127\.0\.0\.1$ "/v2"; # 可选:本地调试强制走新版本
}
<p>split_clients "${remote_addr}AAA" $resource_group {
3% ".v2";</p><ul><li><pre class="brush:php;toolbar:false;"> "";}
server { listen 80; server_name example.com;
location ^~ /static/ {
# 将 /static/js/app.js → /static/v2/js/app.js(当 $resource_group == ".v2")
rewrite ^(/static/)(.*)$ /static$resource_group/$2 break;
root /var/www/html;
expires 1y;
add_header Cache-Control "public, immutable";
}}
-
"${remote_addr}AAA"中的AAA是盐值,避免简单 IP 哈希被预测,增强分配随机性 -
rewrite ... break是关键:仅重写 URI,不跳转,保持 URL 对用户透明 - 务必确认
root路径下存在/var/www/html/static/v2/目录及对应文件,否则 404
如何验证灰度是否生效
不用等线上流量,本地快速验证三步:
- 用不同公网 IP(如手机热点、朋友网络)访问
/static/js/main.js,检查响应头中X-Static-Version(可加add_header X-Static-Version $resource_group;)是否按比例出现.v2 - 在配置中临时把
3%改成100%,再 curl 测试:curl -I http://example.com/static/js/app.js,看返回的Location或实际文件路径是否带v2 - 查 Nginx 日志,过滤
static请求,统计$resource_group字段分布:awk '{print $NF}' access.log | sort | uniq -c
常见坑与绕过技巧
灰度上线最怕“看起来分了,实际没生效”,这几个点高频出问题:
-
CDN 缓存干扰:CDN 回源时可能把所有请求都标为同一个 IP(如 CDN 节点 IP),导致全量走同一组。解法:回源请求头传真实 IP(
X-Real-IP),并在 Nginx 中用split_clients "$http_x_real_ip" ... -
内网 IP 冲突:NAT 后多用户共用一个出口 IP,会被分到同一灰度组。若需更细粒度,可改用
$http_user_agent$remote_addr组合哈希(但牺牲稳定性) -
静态资源未预发布:灰度配置开了,但
/static/v2/目录为空,用户直接 404。建议发布流程中增加校验脚本:diff -r static/ static/v2/ | grep "Only in static/v2"


















