NGINX基于cookie针对同一域名进行分流转发
最新了解到的姿势,结合着新接触 Mac 电脑,第一次做实验,学习之后,特别记录一下。
版本说明
本文写于 2019-08。
未在更高版本上重新验证,请以对应版本的官方文档为准。
# 1. 背景与原理
有时候测试环境会有多套不同的服务(比如 dev、staging、preprod),如果每套都配置独立的域名,客户端需要频繁切换域名,管理成本很高。更优雅的方案是在 NGINX 层面根据请求中的 cookie 信息,将同一域名的流量分发到不同的后端服务。
# 1.1 工作原理
Nginx 的 map 指令可以从变量中提取值并映射到新的变量。结合 $COOKIE_xxx 变量,可以根据客户端携带的 cookie 动态选择上游服务器。
这种方案的优势在于:
- 无需修改客户端代码:浏览器自动发送 cookie,Nginx 根据 cookie 路由
- 无感知切换:用户只需要在测试工具中设置不同的 cookie 值,即可切换环境
- 配置灵活:支持精确匹配、前缀匹配、后缀匹配、正则匹配等多种方式
# 1.2 典型应用场景
A/B 测试:根据用户 cookie 将流量分配到不同版本的服务,验证新功能的效果。
多环境调试:开发人员通过设置特定的 cookie 值,访问测试环境、预发布环境,而无需修改 hosts 文件或使用独立的域名。
灰度发布:新版本上线时,可以先将少量用户的流量导入新服务,观察运行情况后再逐步扩大范围。
很多时候,测试环境可能会有好多套环境,这个时候,如果每套都配置一个对应的域名,会非常麻烦,但是很多时候针对这个问题似乎又没有特别好的方案,新公司新气象,学到新的思路是在 NGINX 层面基于 cookie 来进行不同环境的分流转发,今天就来做一下这个实验。
# 2,环境准备。
因为在新环境,还没有个人自用的测试服务器,Mac 当中做实验又不习惯,于是只能通过 docker 来进行了。
所以需要先安装 docker 环境,这个就不在这里赘述了。
那么,docker 环境准备完毕之后,就可以开始实验了,所谓,docker 在手,天下我有。

# 3,思路说明。
首先跑两个 NGINX 的容器,访问之后会返回不同的结果,然后前端再添加一层 NGINX,代理所有的外部请求,根据 cookie 的不同,分发到不同的后端容器去。
# 4,开始操作。
# 1,先启两个后端容器。
准备工作:
$ mkdir -p /Users/liqilong/docker/nginx
$ cd /Users/liqilong/docker/nginx
$ mkdir test1 test2
$ echo test1 > test1/index.html
$ echo test2 > test2/index.html
2
3
4
5
启动容器:
$ docker pull daocloud.io/library/nginx:1.15.9-alpine-perl
$ docker run --name test1 -v /Users/liqilong/docker/nginx/test1:/usr/share/nginx/html:ro -d -p 8080:80 daocloud.io/library/nginx:1.15.9-alpine-perl
$ docker run --name test2 -v /Users/liqilong/docker/nginx/test2:/usr/share/nginx/html:ro -d -p 8081:80 daocloud.io/library/nginx:1.15.9-alpine-perl
2
3
访问验证:
$ ifconfig en0
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
ether 02:00:00:00:00:01
inet6 fe80::1cf4:9734:2fa8:8234%en0 prefixlen 64 secured scopeid 0x5
inet 192.0.2.11 netmask 0xfffffc00 broadcast 192.0.2.12
nd6 options=201<PERFORMNUD,DAD>
media: autoselect
status: active
$ curl 192.0.2.11:8080
test1
$ curl 192.0.2.11:8081
test2
2
3
4
5
6
7
8
9
10
11
12
# 2,再启动一个前端 NGINX。
因为要做一些相对的配置工作,我这里就用了自己配置的 centos 镜像来做了,事实上仍旧可以利用刚刚那个 NGINX 镜像来做接下来的实验。
$ docker pull registry.cn-hangzhou.aliyuncs.com/eryajf/centos:7.4
$ docker run -itd --name eryajf registry.cn-hangzhou.aliyuncs.com/eryajf/centos:7.4
2
接下来的操作就是进入此容器内部进行了。
$ docker exec -it eryajf sh
sh-4.2# yum localinstall http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
sh-4.2# yum -y install nginx
2
3
添加如下 NGINX 配置:
cat >> /etc/nginx/conf.d/test.conf << EOF
upstream test01 { #此处可以单独写,也可以写在下边map的内容中
server 192.0.2.11:8080 weight=1 max_fails=1 fail_timeout=30s;
}
upstream test02 {
server 192.0.2.11:8081 weight=1 max_fails=1 fail_timeout=30s;
}
upstream root {
server 192.0.2.11:8080 weight=1 max_fails=1 fail_timeout=30s;
}
map $COOKIE_testenv $group { #$COOKIE_testenv的前半部分$COOKIE_是固定格式,后边的testenv则是cookie的key,$group是别名
test1 test01; #表示cookie的value=test1,则转发给test1
test2 test02;
default root;
}
server {
listen 81;
server_name localhost;
access_log logs/access_log;
error_log logs/error_log;
location / {
proxy_pass http://$group$request_uri; #注意此处url的拼接
proxy_set_header X-Forwarded-For $remote_addr;
}
}
EOF
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
然后启动 NGINX:
nginx -t
nginx
2
# 3,访问测试。
这个时候可以通过命令行来模拟请求,然后查看效果。
sh-4.2# curl localhost:81
test1
sh-4.2# curl localhost:81 --cookie "testenv=test1"
test1
sh-4.2# curl localhost:81 --cookie "testenv=test2"
test2
2
3
4
5
6
此处只要是有一个 cookie 名称与内容是符合 nginx 定义的规则的,那么如上规则就是成立的。
sh-4.2# curl localhost:81 --cookie "testenv=test1;user=root;pass=123"
test1
sh-4.2# curl localhost:81 --cookie "testenv=test2;user=root;pass=123"
test2
2
3
4
# 5,其他方面。
另外除了上边的比较固定的方式之外,还有比较灵活的控制方案,主要集中在 url 的匹配上。
# 1,匹配结尾关键字。
需求就是匹配到 cookie 的指定结尾进行分流转发。NGINX 配置如下:
map $COOKIE_testenv $group {
~*1$ 192.0.2.11:8080;
~*2$ 192.0.2.11:8081;
default 192.0.2.11:8080;
}
server {
listen 81;
server_name localhost;
access_log logs/access_log;
error_log logs/error_log;
location / {
proxy_pass http://$group$request_uri;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
然后重新加载 NGINX 配置,请求一下验证效果:
sh-4.2# curl localhost:81 --cookie "testenv=dfhg;user=root;pass=123"
test1
sh-4.2# curl localhost:81 --cookie "testenv=dfhg1;user=root;pass=123"
test1
sh-4.2# curl localhost:81 --cookie "testenv=dfhg2;user=root;pass=123"
test2
2
3
4
5
6
# 2,匹配开头关键字。
与上边的道理是一致的,只不过配置内容更改一下即可。
map $COOKIE_testenv $group {
~*^1 192.0.2.11:8080;
~*^2 192.0.2.11:8081;
default 192.0.2.11:8080;
}
server {
listen 81;
server_name localhost;
access_log logs/access_log;
error_log logs/error_log;
location / {
proxy_pass http://$group$request_uri;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
然后请求一下,验证一下效果:
sh-4.2# curl localhost:81 --cookie "testenv=dfhg;user=root;pass=123"
test1
sh-4.2# curl localhost:81 --cookie "testenv=1dfhg;user=root;pass=123"
test1
sh-4.2# curl localhost:81 --cookie "testenv=2dfhg;user=root;pass=123"
test2
2
3
4
5
6
# 3,匹配包含关键字。
还有一种比较灵活的策略,就是只要包含指定的关键字标识,就往不同的后端进行分流转发,配置如下:
map $COOKIE_testenv $group {
~*.*eryajf1.* 192.0.2.11:8080;
~*.*eryajf2.* 192.0.2.11:8081;
default 192.0.2.11:8080;
}
server {
listen 81;
server_name localhost;
access_log logs/access_log;
error_log logs/error_log;
location / {
proxy_pass http://$group$request_uri;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
然后请求一下,验证一下效果:
sh-4.2# curl localhost:81 --cookie "testenv=A3fklj;user=root;pass=123"
test1
sh-4.2# curl localhost:81 --cookie "testenv=A3fkeryajf1lj;user=root;pass=123"
test1
sh-4.2# curl localhost:81 --cookie "testenv=A3fkeryajf2lj;user=root;pass=123"
test2
2
3
4
5
6
# 6. 坑与边界
常见问题一:Cookie 未携带
如果客户端未携带任何 cookie,将使用 default 分组。确保 default 分组指向正确的后端服务。
常见问题二:正则匹配性能
正则匹配比精确匹配性能稍差,如果匹配规则简单,建议使用精确匹配。
常见问题三:大小写敏感
默认情况下 map 匹配是区分大小写的,如果需要不区分大小写,可以使用 ~* 前缀。
# 7. 进阶:基于 Header 的分流
除了 Cookie,还可以基于请求 Header 进行分流:
map $http_x_env $group {
dev 192.0.2.11:8080;
staging 192.0.2.11:8081;
prod 192.0.2.11:8082;
default 192.0.2.11:8080;
}
2
3
4
5
6
使用方式:
curl -H "X-Env: staging" http://example.com/
# 8. 参考
# 9. 生产环境配置示例
在生产环境中,Cookie 分流通常与其他机制配合使用:
场景:AB 测试
http {
# 从 Cookie 中提取用户分组
map $COOKIE_ab_group $backend {
"A" "backend_a";
"B" "backend_b";
default "backend_default";
}
upstream backend_a {
server 192.0.2.21:8080;
}
upstream backend_b {
server 192.0.2.22:8080;
}
upstream backend_default {
server 192.0.2.20:8080;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://$backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
配置说明:
- 用户首次访问时,后端服务设置
ab_groupCookie(通过代码或 Nginx 脚本) - 后续请求携带该 Cookie,Nginx 根据值路由到不同后端
- 可以实现不同用户看到不同版本的页面
# 10. 性能影响
Cookie 分流的性能开销极低,原因如下:
- 只读取请求头:Cookie 已在 HTTP 请求中,无需额外查询
- 内存操作:map 指令的执行是纯内存操作,速度在纳秒级
- 无状态:不需要维护会话状态,Nginx 本身无状态
相比其他分流方式(如 DNS 轮询、负载均衡器),Cookie 分流的优势是实现简单且生效快。
# 11. 与其他分流方案的对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Cookie 分流 | 无需修改客户端,实现简单 | 依赖 Cookie | A/B 测试、多环境切换 |
| URL 参数分流 | 直观可见 | URL 需要变更 | 调试场景 |
| IP 哈希分流 | 固定用户固定后端 | 无法灵活控制 | 简单灰度 |
| DNS 分流 | 域名级别控制 | 生效慢 | 大规模流量切换 |
Cookie 分流是最灵活且对用户透明的方案,推荐作为首选。
# 12. 常见错误排查
错误一:分流不生效
检查 map 块是否在 http 层定义,变量名是否与实际使用的 Cookie 名称一致。
错误二:循环重定向
确保 upstream 名称与 server 内的 proxy_pass 名称不重复。