nginx实现简单的A-B测试(灰度发布)
灰度发布,现在是很多大项目的一个标配运维特性,我们可以将一个“新的版本代码”发布到集群中的少数几台(组)机器上,以便引入线上少量真实用户进行测试,用于验证产品改进的收益、小规模试错等。nginx提供了“nginx_http_split_clients_module”、“nginx_stream_split_clients_module”,分别适用于http和tcp,可以帮助我们简单实现这些功能,不过如果你需要高度自动化、自主化的特性,或许需要进行一定的扩展和改造。
split模块在0.8版本就已经有了,使用起来也非常简单:
split_clients string $variable {....}
此指令的上下文为http。
1、假如,我们的web项目中提供了2个html页面,其中index_new.html为最新发布页面,index.html为旧页面,为了“避免新页面有BUG,而影响全站用户”、“测试新页面上体验改进是否符合预期”,我们将index页面的访问流量中的10%转发到新页面上,剩余90%继续使用旧页面。
split_clients "${remote_addr}_1qazxsw2" $variant { 10% _new; * ""; } server { ... location / { index index${variant}.html; }
2、假如,我们的web项目中发布了新的功能,为了避免程序BUG导致大规模异常,减少影响面,我们也可以使用spit做“切流量上线”;首先我们需要将线上的机器分为多个group,每个group中可以包含一定数量的server,简单而言,就是一个group就是一个upstream;比如我们30台机器,我们想切10%的流量发布新版本,那么就将3台机器组成一个“灰度组”来发布新程序,其他机器继续运行旧的web程序;当灰度组经过多日的观察,确实没有太大问题,我们则可以取消灰度组,全量上线发布。
split_clients "${remote_addr}_1qazxsw2" $group { 10% gray_group; * main_group; } upstream gray_group { server 10.0.1.11; ... } upstream main_group { .... } server { ... location / { proxy_pass http://${group}; }
上述配置文件中,我们可以看到split模块的使用方式,“split_clients”指令接受2个参数,nginx将会根据第一个参数的值使用MurmurHash2算法计算hashcode,hashcode值域为0~4294967295(即2<<31 - 1),通常第一个参数是一个动态值,比如用户的ip、session_id等。10%是表示一个区域,如果第一个参数的hashcode值,在0 ~ 4294967295 *10%之间,则将“_new”赋值给$variant;20%表示第一个参数的hashcode值,在4294967295 * (10% ~ 20%)区间时....依次轮推,"*"表示上述区间都没有覆盖的值域,将使用的值。
我们发现这种方式的灰度发布,其实流量导入的比例并非严格。如果你的服务多次灰度发布,可能会导致某些IP总是在灰度区域,有时候我们需要调整spit第一个参数的随机数因子,本例中"${remote_addr}_1qazxsw2",“_1qazxsw2”这个补充字符串就是随机因子,每次灰度发布,我们需要修改这个值以避免上一次灰度的IP再次进入灰度,当然这个操作也是没有太大必要。
通常我们还有一些更加苛刻的要求,比如强制要求某个IP、用户ID进入灰度区域。那么使用spit模块就显得有些不太适应,要么使用nginx的geo组件将相应的IP、uid添加到geo文件中(还需要后端程序在cookie输入相应的uid值,以便nginx获取和判断)。对于这种有很多“个性化”条件的,我们可以使用nginx + lua + redis (memcached)实现,其中lua作为脚本语言来做条件判断工具以及使用redis客户端操作数据,redis用于存储需要灰度的用户信息,此外可能还需要开发一个后端程序来运营redis中的数据,比如将某个uid加入redis,lua从cookie中读取uid并检查redis中是否存在,如果存在则进入灰度区域。