可直接用 $http_x_business_id 读取 X-Business-ID 请求头(Nginx 自动标准化为小写下划线格式),在 http 块中用 map 指令匹配 UUID、test-前缀等规则映射为 $is_canary 灰度标识,再通过 proxy_pass 动态路由至预定义的 backend_0 或 backend_1 upstream。

map 指令怎么从请求头提取业务 ID 并映射为灰度标识
直接用 $http_x_business_id 读取请求头是可行的,但必须注意:Nginx 默认会把 header 名中的下划线转成短横(X-Business-ID → $http_x_business_id),而如果客户端发的是 X-Business_Id 或 x-business-id,变量名仍为 $http_x_business_id —— Nginx 统一做了标准化。所以别去猜变量名,统一用小写+下划线格式。
关键点在于 map 必须定义在 http 块里,不能放在 server 或 location 中。否则会报错:"map" directive is not allowed here。
http {
map $http_x_business_id $is_canary {
default 0;
~^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$ 1; # UUID 格式
~^prod-[0-9]+$ 0;
~^test-[0-9]+$ 1;
}
}
- 正则匹配优先级从上到下,第一个匹配即生效;
default是兜底值,不是“最后才匹配” - 不要用
^test.*$这类宽泛正则,容易误伤生产 ID(比如testdrive-123被当成灰度) - 空字符串、缺失 header 时,
$http_x_business_id为空,会走default分支 —— 所以默认值设为0更安全
如何让 upstream 根据 $is_canary 动态选择灰度节点
不能直接在 upstream 块里用变量(Nginx 不支持),得靠 split_clients 或 map + proxy_pass 组合。后者更可控、无随机性,适合精准染色。
推荐做法:用两个独立的 upstream,再用 proxy_pass 根据 $is_canary 切流:
upstream backend_prod {
server 10.0.1.10:8080;
server 10.0.1.11:8080;
}
<p>upstream backend_canary {
server 10.0.2.5:8080; # 灰度实例,可能带新版本 tag
}</p><p>server {
location / {
proxy<em>pass <a href="https://www.php.cn/link/65b5b8d1f89bf53a5713bc3afdd83e9e">https://www.php.cn/link/65b5b8d1f89bf53a5713bc3afdd83e9e</a></em>$is_canary;</p><h1>注意:这里不能写成 "backend_${is_canary}",Nginx 不支持 ${} 插值</h1><pre class='brush:php;toolbar:false;'> # 必须提前定义好 backend_0 和 backend_1 两个 upstream
}
}
- 必须预先定义
upstream backend_0和upstream backend_1,否则proxy_pass解析失败 - 也可以用
if ($is_canary = 1) { proxy_pass https://www.php.cn/link/65b5b8d1f89bf53a5713bc3afdd83e9e_canary; },但if在location中有性能隐患,且不支持嵌套 proxy_pass - 如果灰度比例不是 100%,而是按 ID 哈希分流(如 5%),就该换用
split_clients,但那就脱离“精准染色”了
为什么加了 map 后所有请求都进了灰度?常见配置陷阱
最常踩的坑是 header 名拼写错误或大小写干扰。Nginx 对 $http_x_* 变量名严格区分大小写,但 header 实际传输是不区分大小写的 —— 所以问题往往出在开发侧传错了 key 名,比如前端写了 X-BusinessID(少了个下划线),Nginx 就生成 $http_x_businessid,而你的 map 还在查 $http_x_business_id,结果永远走 default。
- 用
log_format把原始 header 打出来验证:log_format debug '$http_x_business_id ←→ $is_canary'; - 检查是否启用了
underscores_in_headers on;—— 默认是 off,如果 header 含下划线(如X_Business_ID),Nginx 会直接丢弃该 header -
map块里不能调用其他变量(比如$arg_id),也不能嵌套map;所有逻辑必须扁平化表达 - 修改
map后 reload Nginx 即可生效,无需 restart,但旧连接不会重算 —— 这是预期行为
灰度流量需要透传业务 ID 到后端服务吗
需要。否则后端日志、链路追踪、AB 实验分组都丢失上下文。但不能只靠 proxy_set_header X-Business-ID $http_x_business_id; —— 如果原始 header 不存在,这个值就是空,后端收不到任何标识。
更稳妥的做法是:只在染色命中时才透传,并带上明确标记:
proxy_set_header X-Canary "true"; proxy_set_header X-Business-ID $http_x_business_id; <h1>或者合并成一个 header:</h1><p>proxy_set_header X-Traffic-Tag $is_canary$http_x_business_id;</p>
- 后端应优先信任
X-Canary这类显式标记,而不是反向解析业务 ID 规则 - 避免把
$is_canary直接当业务 ID 用 —— 它只是开关,不是原始输入 - 如果后端是 Java Spring Cloud,注意
ServerHttpRequest默认会过滤掉下划线 header,需配置allowed-headers
实际部署时最容易被忽略的,是 header 名标准化和 Nginx 的 underscores_in_headers 开关配合 —— 两者不一致,染色就完全失效,而且现象是“看起来配置都对,但就是不生效”。


















