如何在tengine/nginx层做ABtest

栏目: 服务器 · Nginx · 发布时间: 5年前

内容简介:我们在做业务项目需求的时候经常会做ABtest,在发布时也会做灰度发布,通常这些ABtest都是在同一应用上做的,即我们在A应用上开发新版的代码,并通过代码控制分桶和打点。但是我们也经常遇到这种情况:新版实验与老版不在同一个应用上,那么之前的方案就无法做切流了。- 我们希望 分流解决方案将一部分流量分到新应用,另一部分流量分到老应用,且该流量是可以控制的

我们在做业务项目需求的时候经常会做ABtest,在发布时也会做灰度发布,通常这些ABtest都是在同一应用上做的,即我们在A应用上开发新版的代码,并通过代码控制分桶和打点。但是我们也经常遇到这种情况:新版实验与老版不在同一个应用上,那么之前的方案就无法做切流了。

1. 问题定位:

- 我们希望 分流解决方案将一部分流量分到新应用,另一部分流量分到老应用,且该流量是可以控制的

- 我们希望新老版本有明显的标识来区分用户命中的是新版还是老版(即打点)

- 假如“我们严谨的PD们”想要看AB对比数据,我们还要比较方便的从报表分区新老版命中

上述三条其实基本构成了一个简易的AB系统,类似我们常用的buckettest、BTS等,当然 BTS此类实验平台还有一个比较完善的控制台来控制切流和报表汇总。

2. 问题解决:

一般此类跨应用切流都会有类似的应用依赖访问结构:

如何在tengine/nginx层做ABtest

虚线是新版的访问路径,对于γ类型,如果要做ABtest,需要在上层vip/lvs层做,过于复杂,因此可以转化成β的结构,或者将B中的 新老应用层级交换一下。对于β形态,我们完全可以将老应用A 当成α形态中的老应用,因此我们只需对α形态进行讨论。

1)思路一:通过发布批次控制切流节奏

这是我们做业务页面迁移时比较常用的方法,即在应用M层修改 反向代理逻辑,使请求转发到新应用B,并通过发布的批数来控制切流节奏。

优点 : 修改方便,只需发布一次M,修改出错成本低;

缺点 : 无法控制用户访问新版老版,只能由应用M的lvs或VIPServer的负载均衡做随机分流,如果遇到流量不均衡问题,切流会十分不均衡。业务效果无法对比,因为用户会时而刷出新版,时而刷出老版。发布周期长,需要长时间占用发布流程。

上述方案一般用于 只迁移,不做业务数据对比的技术改造升级项目。

2)思路二:在应用M层的tengine/nginx层做分流

优点 : 分流策略可以根据cookie、ip、ua等灵活配置,可以比较精确的控制流量分布;

缺点 : 需要至少发布两次,配置较为复杂,容易搞出问题

如果是仅仅进行技术迁移,一般用方案一即可,如果遇到需要精确流量控制或者需要准确的技术和业务数据对比那方案二无疑是比较好的

那么我们就研究一下如何在tengine里面做切流吧:

3. 在tengine/nginx 层做AB test

1)分流器设计:

使用 if语句做分流器:

例如我们对/abc/ path下的请求,cookie中含有version=1的转发到老应用,对version=2的转发到新应用:

set $version "default";
    if ($http_cookie ~* "version=1") {
        set $version v1;
    }

    if ($http_cookie ~* "version=2") {
        set $version v2;
    }

  location /abc/ {
    if ($version = v1) {
        proxy_pass http://A_APP;
    }
    if ($versuib = v2) {
        proxy_pass http://B_APP;
    }
  ......
  }
复制代码

使用 map做分流器:

例如我们对/abc/ path下的请求,cookie中含有version=1的转发到老应用,对version=2的转发到新应用:

map $COOKIE_version $version {
    1 v1;
    2 v2;
    default default;
 }
 location /abc/ {
    if ($version = v1) {
        proxy_pass http://A_APP;
    }
    if ($versuib = v2) {
        proxy_pass http://B_APP;
    }
    ...
  }
复制代码

注: $COOKIE_version 是nginx的语法,指获取cookie中key=version的值

使用split_clients 方法:

##下面在http 块中
split_clients "$COOKIE_cna" $appversion {
        50%     v1;
        *       v2;
    }

##下面在server块中
location /abc/ {
    if ($version = v1) {
        proxy_pass http://A_APP;
    }
    if ($versuib = v2) {
        proxy_pass http://B_APP;
    }
    ...
 }
复制代码

注:cna是我们常用的cookie分流的值,每一个用户的cna是一样的,保证能按照cookie进行分流

使用 lua 编写分流脚本:

init_by_lua '  
               mmh2 = require "murmurhash2"  
      '; 

      location /abc/ {
        set $version "default";

        set_by_lua '
            local cna = ngx.var.cookie_cna;
            local hash_code = mmh2(cna) % 100;
            if hash_code >= 50 then
                ngx.var.version = v1;
            else 
                ngx.var.version = v2;
            end
        ';

        if ($version = v1) {
            proxy_pass http://A_APP;
        }
        if ($versuib = v2) {
            proxy_pass http://B_APP;
        }
        ...
      }
复制代码

注:mmh2 = require "murmurhash2" 为引入第三方hash函数:murmurhash2;

处理第一次请求时无cookie情况:

按照惯例,第一次无cookie的情况会随机一个数来进行分流,第二次来访问时再根据cookie进行重新分流,虽然会导致有1/2的概率会导致用户第一次访问和第二次不一致,但是由于我们的业务第一次无cookie访问的用户大部分是新用户,有超过60%的用户没有第二次访问,因此这个比例是比较小的。

如果要做到绝对的精确分流,就要对无cookie的用户增加一个cookie来标示其所属的桶。两种方法分别对应:

set_by_lua '
    local cna = ngx.var.cookie_cna;

    if cna == '' or cna == nil then
        math.randomseed(1);
nvx.var.cookie_cna= math.random(0,100);
    end
  ';
复制代码

@@需要进行精确分流的方法:

set $random_num 101;
set_by_lua '
    local cna = ngx.var.cookie_cna;

    if cna == '' or cna == nil then
        math.randomseed(1);
 nvx.var.random_num = math.random(0,100);
    end
  ';
if ($random_num != 101) {
    add_header Set-Cookie "random_num=$random_num;";
}

## 在后续的判断中首先根据random_num进行分流,再根据cna进行分流
复制代码

2)分流比例控制:

由于上面1) 中的默认都是设置的50%比例切流,如果“我们可爱的PD”要求2:8分咋整?要么我们改一下上面的比例重新发布一下,要么引入实时干预的某个东西。当然重新发布对于我们懒惰的 程序员 来说是无法忍受的。还好tengine支持访问diamond和tair:

参考 tengine使用文档

http {
    diamond_server jmenv.tbsite.net:8080;    
    diamond_app group dataid $content $version;

    split_clients "$COOKIE_cna" $appversion {
        $content     v1;
        *       v2;
    }
...
}
复制代码

注: 上面代码未经线上测试,如要使用,请自行测试验证。content变量就是从diamond里面读取到的设置的ratio啦,可以设置为0%,10%,50%等等。

3)分流打点与数据查看:

只分流不打点,业务数据没法看啊,所以我们得想办法把新版和老版本区分开。我们可以在nginx里面搞定或者在新老版本的应用里面 打上对应的业务点位。在我们的实践中,是采用的第二种方案,是为了延续之前做BT的参数,使用resion_trace 字段中的cid打点。你也可以在新版中加个cookie: bts_v = 1, = 2, 然后在日志报表中 捞对应的cookie来判断。

总结

通过上述的三点,我们可以搭建起一套比较完整的切流、动态调整分流比例、查看业务数据 的跨应用切流方案了。

有人会问,在一个应用内的AB test可以在tengine/nginx 层做吗?我的建议是:小伙子,不要花样作死,nginx一不小心就会改挂的哦。

如果你想使用nginx+lua的 超强并发能力,或者对nginx、lua有很深的造诣,或者你想写一堆代码并成为“不可替代的程序员”,可以尝试在tengine/nginx 层做很多业务逻辑,如AB test、连数据库、拼装页面等等。

参考文献:


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

UNIX网络编程 卷2

UNIX网络编程 卷2

W.Richard Stevens / 人民邮电出版社 / 2009-11 / 89.00元

《UNIX网络编程 卷2:进程间通信(英文版·第2版)》是一部UNIX网络编程的经典之作。进程间通信(IPC)几乎是所有Unix程序性能的关键,理解IPC也是理解如何开发不同主机间网络应用程序的必要条件。《UNIX网络编程 卷2:进程间通信(英文版·第2版)》从对Posix IPC和System V IPC的内部结构开始讨论,全面深入地介绍了4种IPC形式:消息传递(管道、FIFO、消息队列)、同......一起来看看 《UNIX网络编程 卷2》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具