
你要上线一个新功能。重构了代码,改了数据库结构,测试环境跑了一周,没问题。但你还是不敢直接全量上线。万一挂了,用户全炸。
如果能让1%的用户先试,没问题再逐步扩大到100%,就好了。
这就是灰度发布。Nginx能帮你实现。不需要改代码,加几行配置就行。
先看一个数据
某大型网站统计,引入灰度发布机制后,线上故障导致的大范围服务中断减少了70%。灰度发布让“坏版本”只影响一小部分用户,发现问题可以秒级回滚,不影响绝大多数人。
你不灰度,就是在赌。赌你的代码没bug,赌你的测试覆盖了所有场景,赌用户不会用你没想到的方式操作。
灰度发布的三种常见策略
| 策略 | 适用场景 | 实现方式 |
|---|---|---|
| 按用户ID分流 | 用户维度灰度,体验一致 | split_clients对user_id取模 |
| 按IP分流 | 内部测试、地域灰度 | geo或map匹配IP段 |
| 按Cookie/Header分流 | 指定测试用户 | map读取Cookie值 |
核心思路:把流量分成两份,一份去新版本,一份去老版本。Nginx作为反向代理,根据某个标识(用户ID、IP、Cookie)决定请求发到哪个后端。
配置一:按用户ID分流
最常用的方式。把用户ID哈希后取模,比如前10%的用户走新版本。
nginx
# 定义两个后端
upstream backend_old {
server 192.168.1.10:8080;
}
upstream backend_new {
server 192.168.1.11:8080;
}
# 根据user_id参数分流
split_clients "${arg_user_id}" $backend {
10% backend_new;
* backend_old;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://$backend;
proxy_set_header Host $host;
}
}
split_clients使用MurmurHash2对${arg_user_id}(URL参数中的user_id)做哈希,按百分比分配。同一个user_id永远分配同一个后端,用户体验一致。
触发灰度:http://api.example.com/getUser?id=12345,id的后几位决定走新老版本。用户无感知,不需要前端配合。
配置二:按Cookie分流(指定用户)
给特定用户打标签,让他们走新版本。比如内部员工、种子用户。
nginx
# 读取Cookie中的gray_flag
map $cookie_gray $backend {
default backend_old;
"on" backend_new;
}
server {
location / {
proxy_pass http://$backend;
}
}
实现步骤:给测试用户的浏览器设置一个Cookiegray=on,他们的请求自动走新版本。其他人不受影响。Cookie设置可以在登录时根据用户等级下发,或手动通过浏览器开发者工具添加。
javascript
// 前端设置Cookie document.cookie = "gray=on; path=/; max-age=86400";
配置三:按IP分流
nginx
# 定义IP段
geo $gray_ip {
default 0;
192.168.1.0/24 1; # 公司内网走新版
10.0.0.0/8 1; # 办公网
}
map $gray_ip $backend {
0 backend_old;
1 backend_new;
}
内网IP走新版本,用于内部测试。外部用户还是老版本,测试通过后再逐步开放。
配置四:百分比流量逐步递增
灰度发布是一个过程:1% → 10% → 50% → 100%。每次改配置,重载Nginx。
nginx
split_clients "${arg_user_id}" $backend {
1% backend_new; # 第一阶段:1%
* backend_old;
}
上线后没问题,改配置:
nginx
split_clients "${arg_user_id}" $backend {
10% backend_new;
* backend_old;
}
nginx -s reload热重载,连接不中断。
逐步放量到100%后,把proxy_pass直接指向新版本,下线旧后端配置。
回滚:发现问题,把百分比改回0%,重载,秒级回滚。
进阶:结合多种条件
复杂场景下,可组合使用:优先按Cookie,没有Cookie再按user_id。
nginx
map $cookie_gray $backend {
"on" backend_new;
default backend_old;
}
split_clients "${arg_user_id}" $backend_fallback {
5% backend_new;
* backend_old;
}
# 如果Cookie没设,用fallback
set $backend_target $backend;
if ($backend_target = "backend_old") {
set $backend_target $backend_fallback;
}
proxy_pass http://$backend_target;
逻辑:有Cookiegray=on走新版,否则按5%比例分流。
注意事项
1. 区分状态(session)问题
用户灰度期间产生了新数据(数据库、Redis),一旦回滚到旧版本,新版格式的数据旧版可能读不懂。建议灰度只影响读请求,写请求统一走老版本,或确保新旧版本数据完全兼容。
2. 数据库兼容
新旧版本同时存在,数据库schema必须兼容两者。不能在新版里加一个旧版不认识的字段就发布。
3. 缓存隔离
不同版本的缓存key要区分,避免新版写了缓存,老版读到错误格式的数据。
4. 监控
灰度期间要分别监控新旧版本的错误率、响应时间。Nginx可以配置不同upstream的日志分开记录,或用$upstream_addr变量标记。
真实案例
一个SaaS公司,重构了核心API。用Nginxsplit_clients指令,先切5%流量到新服务。观察了两天,错误率无变化,响应时间还快了10%。逐步提升到20%、50%、100%。全程无用户感知。
他们说:“没有灰度,我们绝对不敢这么重构。”
最后一句
灰度发布不是大公司的专利。Nginx几行配置就能实现。不用改代码,不用引入新组件。
下次上线重要功能,别直接全量。先给1%用户试试。有问题,改个配置就回滚了。没问题,逐步放大到100%。
你的用户不需要知道你在灰度。他们只需要服务稳定。灰度让你既稳定又快速。




