背景
由于业务需求我们需要根据client_ip
对部分接口做一个简单的 ab test,将部分流量导新的服务上。基于之前做 WAF 的经验,制定了大体方案:根据 client_ip
做 hash, 根据 hash 值来确定是请求新后端还是原来的后端服务。
在说明详细方案前,先说明一下现状。当前的配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| location ~ ^/api/rest/v\d+/Login { rewrite ^(.*)$ /loginServer$1 break;
proxy_http_version 1.1; proxy_next_upstream error timeout invalid_header; proxy_redirect off;
proxy_set_header Host $http_host; proxy_set_header Clientip $http_clientip; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Scheme $scheme; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Connection "";
proxy_pass http://test.fanruo.net; }
|
其中,/loginServer
是当前服务路径前缀,/loginServerNew
是新服务路径前缀。所以,具体需求可以描述为,10% 的用户使用/loginServerNew
服务,90% 的用户使用/loginServer
服务。
具体方案
- 根据字符串 client_ip 转成长整数 ip_num;
- 对 ip_num 按 100 取模,取最后两位 hash;
- 当 hash < 10 时走
/loginServerNew
服务, 设置 tag="New"
, 否则 tag=""
- 改造
rewrite
指令为 rewrite ^(.*)$ /loginServer$tag$1 break;
对应配置为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| location ~ ^/api/rest/v\d+/Login { set $tag ""; access_by_lua_file conf/lua/abtest.lua; rewrite ^(.*)$ /loginServer$tag$1 break;
proxy_http_version 1.1; proxy_next_upstream error timeout invalid_header; proxy_redirect off;
proxy_set_header Host $http_host; proxy_set_header Clientip $http_clientip; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Scheme $scheme; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Connection "";
proxy_pass http://test.fanruo.net; }
|
对应 lua 脚本(conf/lua/btest.lua):
1 2 3 4 5 6 7 8 9
| local client_ip = ngx.var.http_clientip or ngx.var.remote_addr local o1,o2,o3,o4 = client_ip:match("(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)" ) local ip_num = 2^24*o1 + 2^16*o2 + 2^8*o3 + o4 local hash = ip_num % 100
ngx.var.tag = "" if hash > 10 then ngx.var.tag = "New" end
|
问题
按照上述实现,最终总是走原来的服务/loginServer
。通过分析 tag 的值一直是 “”,因此,需求不能满足。
原因分析
通过分析,由于之前 WAF 是通过直接修改 proxy_pass 参数来完成的,而 proxy_pass 对应 content phase,该阶段在 access phase 之后,因此上面的配置没有问题。而本需求是在 rewrite phase,因此需要在该阶段之前修改对应变量 tag 只能在 set phase 来完成 nginx 变量修改操作。
解决方案
跟进上述分析,修改 nginx.conf 配置和 lua 脚本如下:
lua 脚本修改为:
1 2 3 4 5 6 7 8 9 10 11
| local client_ip = ngx.var.http_clientip or ngx.var.remote_addr local o1,o2,o3,o4 = client_ip:match("(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)" ) local ip_num = 2^24*o1 + 2^16*o2 + 2^8*o3 + o4 local hash = ip_num % 100
local tag = "" if hash > 10 then tag = "New" end
return tag
|
nginx.conf 配置修改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| location ~ ^/api/rest/v\d+/Login { set_by_lua_file $tag conf/lua/abtest.lua; rewrite ^(.*)$ /loginServer$tag$1 break;
proxy_http_version 1.1; proxy_next_upstream error timeout invalid_header; proxy_redirect off;
proxy_set_header Host $http_host; proxy_set_header Clientip $http_clientip; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Scheme $scheme; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Connection "";
proxy_pass http://test.fanruo.net; }
|