4. 基于Nginx的灰度发布:split_clients模块实现权重灰度

灰度发布里最头疼的问题是什么?我个人认为是流量切分。你想想看,如果每次改完配置都要手动控制比例,运维压力太大了。Nginx自带的 split_clients 模块可以帮我们搞定这件事,简单又直接。

4.1 split_clients 的工作原理

说白了,这个模块就是根据客户端请求的某个变量算一个哈希值,然后按照你设定的百分比范围,把请求分到不同的上游组。哈希计算是确定性的,同一个客户端只要变量不变,每次都会被分到同一个组。我习惯用 $cookie_user_id 或者 $http_x_forwarded_for 做变量,这样灰度用户能稳定留在目标版本里。

核心逻辑:split_clients 把 0%~100% 的区间切成若干份,每一份对应一个后端组。Nginx 根据哈希值落在哪个区间,就转发到对应的 $upstream 变量里。

4.2 配置示例:20%流量给灰度版本

举一个实际例子。我在一次移动端 API 灰度中用了下面这套配置,效果很不错:

upstream stable {
    server 10.0.1.10:8080 weight=5;
    server 10.0.1.11:8080 weight=5;
}

upstream canary {
    server 10.0.2.10:8080 weight=1;
    server 10.0.2.11:8080 weight=1;
}

split_clients "${remote_addr}${http_user_agent}" $upstream {
    20%    canary;
    *      stable;
}

server {
    listen 80;
    location / {
        proxy_pass http://$upstream;
    }
}

这里我用了 $remote_addr + $http_user_agent 做哈希变量,确保同一个设备、同一个浏览器,请求始终落在同一个上游组里。注意 * 表示剩下的 80% 流量全都走 stable。

4.3 避坑指南

坑一:代理环境下的客户端IP – 我刚开始用 $remote_addr 时踩过雷。如果前面有 CDN 或 LVS,所有客户端看到的 $remote_addr 都是代理IP,哈希结果都一样。建议改成 $http_x_forwarded_for 或 $cookie_user_id,或者提取真实IP。

坑二:权重调整后的“雪崩” – 你修改了 split_clients 的百分比,Nginx 必须 reload 才能生效。reload 时原来在 canary 里的长连接可能会被断开重新分配。我曾经碰到过灰度用户突然跳回稳定版,排查了半天才发现是 reload 导致哈希值重新计算。如果你的业务对会话连续性要求高,建议在非高峰期操作,或者用 ip_hash 做补充。

坑三:多个灰度版本的切分 – 一次灰度跑多个版本怎么搞?嵌套 split_clients 或者用两次判断。我一般这样做:先切出 10% 给灰度总体,再在灰度内部切 50% 给 v2.1、50% 给 v2.2。注意变量要一致,否则同一个人可能被分到不同版本。

4.4 实战建议

推荐用 Cookie 做哈希变量 – 在客户端种一个 ab_test 的 cookie,值为随机字符串。这样即使客户端 IP 变化(比如手机切换 Wi-Fi),灰度状态依然保持。我在电商促销场景就是这么做的,效果很稳定。

结合健康检查 – split_clients 只做流量切分,不关心后端是否健康。你需要在 upstream 里配上 max_fails=3 fail_timeout=30s,否则灰度版本挂了,请求会被转发到 $upstream 上,而这个变量可能已经失效——我吃过这个亏。

最后,上一张图帮大家梳理整个流程。

split_clients 灰度流程图 客户端请求 Nginx 接收请求 split_clients 哈希计算 80% stable 上游 20% canary 上游 响应返回客户端 哈希变量决定分组 (IP / Cookie / UA)

看到没?整个链路很清晰:客户端请求进来,Nginx 根据哈希值判断它属于哪个区间,然后直接发到对应的上游组。其实 split_clients 最大的优点就是配置简单、性能开销小,你只需要把握好变量选择和 reload 时机,就能平稳地进行灰度发布。