为什么需要abtest

  线上交易系统快发展,业务功能不断迭代,每周按固定频次上线新功能,难免会有一些BUG,全量上线,出现错误后回滚,导致业务单量损失,我们要将这种损失减少或者尽量降低,这就是需要abtest的原因。关于蓝绿部署,灰度发布,金丝雀等应用部署方案不作讨论,最终原理都是一样的,即通过较少量用户体验来发布某些应用新功能。

实现思路

  abtest对于业务开发来说,最好是独立的,也就是我们需要在业务开发之外实现,无感知切入abtest,同时注意保持业务一致性,例如在某一时期,a用户始终看到A版,B用户始终看到B版。最终我们选用nginx+lua方案,通过在nginx中执行嵌入的lua脚本,动态计算upstream,将不同的用户导向不同的程序版本,达到abtest的目的。

具体实现

  我们通过提取某一个特征cookie标识用户,该cookie在一定周期内针对同一个用户不是随意改变的。假如存在这个cookie,名称为__abc=testuser.123123,如果cookie值为数值化,可以直接进行模运算取余,如果是字符型,先进行一个hash运算得到数值,再进行模运算取余。

  如果业务系统不存在特征cookie,条件允许可以在网站域下种一个新的cookie。

  数据流示意图如下:

  用户b的cookie特征提取为001,跟配置的分流比例300比较,符合条件,将upstream改为b.domain.com, 用户b一直访问新版本程序。

nginx安装lua模块

  lua-nginx-module官方文档 ,请参考https://github.com/openresty/lua-nginx-module#installation,也可以直接安装openresty。

nginx conf配置

lua_package_path "/XXXX/servers/lualib/?.lua;;";
lua_package_cpath "/XXXX/servers/lualib/?.so;;";#dns解析服务器,如果redis使用域名连接,可能需要配置dns
resolver 192.168.2.2 192.168.2.3;#初始化全局变量,包括是否启用分流,流量切换比例, 默认为false不启用,流量切换比例0,不分流
init_by_lua_file        /XXXX/conf/abtesting/init.lua;#定时从redis中刷新 是否启用分流  和  流量切换比例值
init_worker_by_lua_file   /XXXX/conf/abtesting/worker.lua;#默认A版
upstream tomcat_a.domian.com {server 127.0.0.1:1601  weight=100 max_fails=2 fail_timeout=30s ;server 192.168.0.1:80  weight=1 max_fails=2 fail_timeout=30s ;
}#新功能B版
upstream tomcat_b.domain.com {server  192.168.0.2:80  weight=100 max_fails=2 fail_timeout=30s ;server  192.168.0.3:80  weight=1 max_fails=2 fail_timeout=30s ;
}server {listen          80;set $default_backend 'tomcat_a.domain.com';location / {proxy_next_upstream     http_500 http_502 http_503 http_504 error timeout; proxy_set_header        Host  'y.domain.com';proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;expires                 0;set $backend $default_backend;#此处计算可能会修改backend这个nginx变量,也就是变量修改了upstream#具体执行逻辑是提取用户特征,也就是__abc这个cookie值,是否满足具体规则rewrite_by_lua_file '/XXXX/conf/abtesting/diversion.lua';proxy_pass http://$backend;}}

初始化脚本

global_configs = {["divEnable"] = false,  -- 分流开关,true表示开启["newTrafficRate"] = 0,  -- 分流比例,0-1000, 1000表示全部流量,100%["redis"] = {ap_host='192.168.1.10',  -- redis主机ip或者是hostap_port=6379,            -- redis主机端口ap_key='testToken'       -- redis连接密码}
}

定时任务脚本

-- 每隔10秒定时执行,可以自行调整定时任务间隔
local start_delay = 10
local new_timer = ngx.timer.at
local log = ngx.log
local ERR = ngx.ERR
local refresh
local get_redis
local close_redis-- redis中分流开关key
local switch_key = "abtest:switch:global"
-- redis中 分流比例key
local traffic_key = "abtest:limit:traffic"-- 连接redis
get_redis = function()local redis = require "resty.redis"local red = redis:new()local ok, err = red:connect(global_configs['redis']['ap_host'],global_configs['redis']['ap_port'])if ok and global_configs['redis']['ap_key'] thenok, err = red:auth(global_configs['redis']['ap_key'])endreturn red, ok, err
end-- 关闭redis连接
close_redis = function(red)if not red thenreturnendlocal ok, err = red:close()if not ok thenngx.log(ngx.ERR,"fail to close redis connection : ", err)end
end-- 真实执行的任务
local function do_refresh()local red, ok, err = get_redis()if not ok thenlog(ERR, "redis is not ready!")returnendlocal traficLimitStr, err = red:get(traffic_key)-- 从redis中刷新 开关 值local enable, err = red:get(switch_key)if err thenlog(ERR, err)elseif ngx.null ~= enable thenglobal_configs["divEnable"] = ("true" == enable) and true or falseendend-- 从redis中刷新 流量比例 值local trafficLimitStr, err = red:get(traffic_key)if err thenlog(ERR, err)elseif ngx.null ~= trafficLimitStr and tonumber(trafficLimitStr) > 0  thenglobal_configs["newTrafficRate"] = tonumber(trafficLimitStr)log(ERR, "update newTrafficRate: ", global_configs["newTrafficRate"])endendreturn close_redis(red)
end-- 任务执行与下次延时处理
refresh = function(premature)if not premature thendo_refresh()local ok, e = new_timer(start_delay, refresh)if not ok thenlog(ERR, "failed to create timer: ", e)returnendend
end-- 程序入口,第一次nginx timer at定时执行
local ok, e = new_timer(start_delay, refresh)
if not ok thenlog(ERR, "failed to create timer: ", e)return
end

分流计算脚本

if not global_configs["divEnable"] thenreturn
endlocal abc = ngx.var.cookie___abcif abc then -- abc这个cookie可能是 123123.0xab23eff1,或者是 1231123.123123123这种,我们提取第二段值的最后3个字符,可能是10进制或者16进制数字,最终值可能会大于1000,所以取余local v = ngx.re.match(abc,  [[^\d+\.([0-9a-fA-FxX]+)([0-9a-fA-F]{3})\.]]) if v and v[2] thenlocal ckVal = (tonumber(v[2]) or tonumber(v[2], 16) ) % 1000if ckVal and (ckVal < global_configs["newTrafficRate"]) then  ngx.var.backend = "tomcat_b.domain.com" end  endend 

最后我们简单做个操作界面,用于动态改变redis中的值


点击切换开关,改写流量切换比例

其他

  如果cookie是字符串,可以先进行hash运算,下面是一个基于ffi的hash实现可以参考下,文件名是murmurhash2.lua

local ffi      = require "ffi"
local ffi_cast = ffi.cast
local C        = ffi.C
local tonumber = tonumberffi.cdef[[
typedef unsigned char u_char;
uint32_t ngx_murmur_hash2(u_char *data, size_t len);
]]return function(value)return tonumber(C.ngx_murmur_hash2(ffi_cast('uint8_t *', value), #value))
end

  调用示例代码方式如下

local mmh2 = require("abtesting.murmurhash2")-- 对string类型的特征cookie进行hash计算,hash函数是nginx默认实现ngx_murmur_hash2
local hash = mmh2(uid)
local suffix = hash % 1000;

微信关注公众号获取更多精彩内容

一种简单可行的abtest流量切换实现方案相关推荐

  1. 一种简单可落地的分布式事务实践方案

    欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析.实际应用.架构思维.职场分享.产品思考等等,欢迎大家加我微信「java_front」一起交流学习 1 案例背景 用户在电商网站 ...

  2. ABTest流量分发和业界的一些经验

    流量为王的时代,如何精准的利用用户的流量进行分析和产品的迭代?ABTest就是其中不可缺少的一环,那么ABTest是什么呢?下面来一层一层揭开它神秘的面纱. 0.流量分发 在互联网流量的分发模式中,主 ...

  3. ABTest流量分发和业界的一些做法经验

    流量为王的时代,如何精准的利用用户的流量进行分析和产品的迭代?ABTest就是其中不可缺少的一环,那么ABTest是什么呢?下面来一层一层揭开它神秘的面纱. 0.流量分发 在互联网流量的分发模式中,主 ...

  4. GIT将本地项目上传到Github(两种简单、方便的方法)

    GIT将本地项目上传到Github(两种简单.方便的方法) 一.第一种方法: 首先你需要一个github账号,所有还没有的话先去注册吧! https://github.com/ 我们使用git需要先安 ...

  5. git学习(10):Git的使用--如何将本地项目上传到Github(两种简单、方便的方法)

    将本地项目上传到Github(两种简单.方便的方法) 一.第一种方法: 首先你需要一个github账号,所有还没有的话先去注册吧! https://github.com/ 我们使用git需要先安装gi ...

  6. (转载)一种简单而有趣的数据结构——并查集

    一种简单而有趣的数据结构--并查集 作者:goal00001111(高粱) 一个秘密生物武器落到某地区,导致当地村民丧失部分记忆,只认得自己最熟悉的人,而忘记自己是哪个村子的人了.大家汇集到一个广场, ...

  7. 一种简单而有趣的数据结构——并查集

    一种简单而有趣的数据结构--并查集 作者:goal00001111(高粱) 一个秘密生物武器落到某地区,导致当地村民丧失部分记忆,只认得自己最熟悉的人,而忘记自己是哪个村子的人了.大家汇集到一个广场, ...

  8. 一种简单快速的方式实现 Android App 的夜间模式

    博主声明: 转载请在开头附加本文链接及作者信息,并标记为转载.本文由博主 威威喵 原创,请多支持与指教. 本文首发于此   博主:威威喵  |  博客主页:https://blog.csdn.net/ ...

  9. 四种简单的图像显著性区域特征提取方法-----AC/HC/LC/FT。

    四种简单的图像显著性区域特征提取方法-----> AC/HC/LC/FT. 分类: 图像处理 2014-08-03 12:40 4088人阅读 评论(4) 收藏 举报 salient regio ...

最新文章

  1. MySQL高级 之 explain执行计划详解
  2. 职责链模式 php,php Chain of Responsibility 职责链模式
  3. 一个傻瓜式构建可视化 web的 Python 神器
  4. windows中怎么添加定时任务
  5. C#的特性Attribute
  6. 【转】Windows Azure的账户体系
  7. Linux系统编程40:多线程之基于环形队列的生产者与消费者模型
  8. Java zip and unzip demo
  9. 5G产业最新投资机会,25页PPT
  10. Navicat Report Viewer 如何连接到 MySQL 数据库
  11. 杭电4554 叛逆的小明
  12. [bzoj 4939][Ynoi 2016]掉进兔子洞
  13. Swift iOS HealthKit 使用案例: 获取体温列表 HKHealthStore
  14. photoshop中魔棒工具的使用
  15. java版本PID放大/eTerm放大软件介绍
  16. 『原创』老范的来电防火墙v1.0发布了(图文)
  17. PayPal 更换汇率结算方式 降低手续费,PayPal汇率结算 改为 银行汇率结算
  18. 算法与数据结构之队列
  19. 学校计算机科室管理制度,学校科室管理制度资料.doc
  20. 【概率论】- (2)假设检验

热门文章

  1. 百度地图开发之自定义大头针
  2. 第二天u3d的学习!
  3. 11.网络编程的学习总结
  4. android 9.0去掉前置摄像头闪光灯功能
  5. hMailServer安装配置
  6. 太原铁警严厉打击倒卖车票 查获假票60张
  7. css、HTML制作小米商城网页(一)
  8. [深圳]盛情邀请1月25日下午嵌入式技术培训
  9. 单片机入门:LED双向流水灯(原理图+程序+仿真)
  10. 耳鸣是什么原因造成的?