ABTesting system 灰度发布系统调研及部署

灰度发布系统简介

灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式。ABtest 就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。

或者对系统某模块功能做了改变, 希望能通过用户实际的请求数据比对一下新旧版本的效果, 以便于决策产品两个版本孰优孰略.

工作流程

1. 定义目标

区分用户,辅助数据统计,保证灰度发布过程中用户体验的连贯性, 避免用户在新旧版本中跳变。
匿名Web应用可采用IP、Cookie等,
登录的应用可直接采用应用的帐号体系。

2、目标用户选取策略

即选取哪些用户先行体验新版本, 可考虑的因素很多,包括但不限于地理位置、用户终端特性(如分辨率、性能)、用户自身特 点(性别、年龄、忠诚度等)。

3、部署系统

部署新系统、部署用户行为分析系统、设定分流规则、运营数据分析、分流规则调整。

4、分析, 总结, 完善

分析数据, 差异, 进行总结, 对问题点进行完善.

策略

  1. 依据 IP 配置文件: 通过配置文件中的 IP 地址或者 IP 段, 将特定的用户引至新版本, 否则进入旧版本.
  2. 依据 IP 动态分配: 动态分配至不同的版本, 需要数据库做配合, 首次访问的时候写入记录, 后续的访问保持该记录.
  3. 依据 cookie 识别: 动态分配至不同的版本, 不同的版本可以通过cookie进行区分, 首次访问动态将用户导入不同的版本, 后续通过Cookie 进行判断该用户前往的版本.
  4. 根据用户信息分配: 需要有账户体系配合, 根据用户的身份特征, 结合具体的业务需求, 将不同的用户导入不同的版本.

实验

思路

  1. 后端服务器 2 台: serverA, ServerB, ServerA 为主服务器, ServerB 为辅服务器
  2. 设置比率: 默认走 serverA, ServerB 给予 x% 的概率.
  3. 存储使用Redis: prifix 为 upstreamByIP_ , key like upstreamByIP_127.0.0.1
  4. 请求过来之后, 获取请求的IP地址: clientIP, 根据 clientIP 去 redis 中查询看该 IP 是否有对应的 value, 如果有 server 为该 value, 如果没有 getRandomServer, 存储到 redis 中, 为了统计方便, 存储的同时, 加入以 value 为 key 的集合.
  5. 根据 最终的 server 值, 将该 client 分发到 对应的 @server 去.

注意事项

  1. 是否能准确获取用户的 clientIP, 因为 可能有 来自上游的 IP 地址, 这一点得测试一下, 需要能获取到用户的真是的 IP
    地址才行, 这是该方案能得以实施的前提条件.
  2. 配置后端的时候, 需要注意 proxy header 等 属性的转发设置, 不要丢掉重要的 client 传过来的信息.
  3. 要全方位的做好测试工作.

代码

nginx

user  www-data;

worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 102400;

pid   /run/nginx/nginx.pid;


events {
    worker_connections  10240;
    accept_mutex off;
    multi_accept on;
    use epoll;
}

error_log /data/logs/nginx/nginx_error.log;

http {

    server_tokens off;
    sendfile on;
    tcp_nodelay on;
    tcp_nopush on;
    charset utf-8;

    include mime.types;

    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
        '$status $body_bytes_sent "$http_referer" '
        '"$http_user_agent" "$http_x_forwarded_for"';

    keepalive_timeout  75;
    keepalive_requests 32768;

    proxy_next_upstream     error timeout;
    proxy_redirect          off;
    proxy_set_header        Host $host;
    proxy_set_header        X-Real-IP $http_x_forwarded_for;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_connect_timeout   180;
    proxy_send_timeout      180;
    proxy_read_timeout      180;
    proxy_buffer_size       8k;
    proxy_buffers           8 64k;
    proxy_busy_buffers_size 128k;
    proxy_temp_file_write_size 128k;

    client_max_body_size    100m;
    client_body_buffer_size 256k;

    #lua_code_cache off;

    access_log /data/logs/nginx/nginx_access.log;

    server {
      listen 80 default;
      return 404;
    }

    
    include vhosts/* ;
    include conf.d/* ;
}

lua

-- for test
-- ngx.say('hello')

local redis = require "resty.redis"

local getClientIP = function()
    if ngx.req.get_headers()["X-Real-IP"] then
        return ngx.req.get_headers()["X-Real-IP"]
    elseif ngx.req.get_headers()["x_forwarded_for"] then
        return ngx.req.get_headers()["x_forwarded_for"]
    else
        return ngx.var.remote_addr
    end
end

local clientIP = getClientIP()

-- for test
-- ngx.say(clientIP)

local getRandomServer = function()

    local serverA  = 'server1'
    local serverB  = 'server2'
    local rate     = 30

    math.randomseed(tostring(os.time()):reverse():sub(1, 7))
    local random = math.random(1,100)

    if random <= rate then
        return serverB
    else
    return serverA
    end
end

local red = redis:new()
red:set_timeout(1000) -- 1 sec
--local ok, err = red:connect("127.0.0.1", 6379)

local ok, err = red:connect("unix:/tmp/redis.sock")

if not ok then
    ngx.say("failed to connect: ", err)
    return
end

-- ngx.say(getRandomServer())

local res, err = red:get(clientIP)

if not res then
    ngx.say("failed to get clientIP", err)
    return
end

-- if the ip not in redis, then get random server
-- and set it to redis, else set res to server

if res == nil or res == ngx.null then
    server  = getRandomServer()
    -- ngx.say(server)

    red:set(clientIP, server)
    red:sadd(server,clientIP)

else
    server = res
end

-- ngx.say(server)

ngx.exec("@" .. server)

-- put it into the connection pool of size 100,
-- with 10 seconds max idle time
local ok, err = red:set_keepalive(10000, 100)
if not ok then
    ngx.say("failed to set keepalive: ", err)
    return
end

部署过程

1. 部署设备 nginx_proxy :

主要的服务为 openresty , redis, 分别需要下载源码, 编译安装, 设置开机启动脚本, 设置配置文件
编译的需要, 需要安装 libreadline-dev, libncurses5-dev, libpcre3-dev ,libssl-dev perl make build-essential, tcl. 代码管理的需要, 需要安装 git-core
服务管理的需要, 需要安装 sysv-rc-con
防火墙需要配置, ubuntu 防火墙规则持久化的需要, 需要安装 iptables-persistent
shell 操作的需要, 安装了vim, zsh
流量分析的需要, 安装了 nload

2. 部署测试代码

使用已经在本地测试好的 nginx 配置文件和 lua 脚本, 拉取到线上.
在线上配置好后端机器, 首先满足不加载 lua 脚本的时候可以正常的访问.
开启 lua 脚本,进行调试, 调试的过程中, 使用 8080 端口.
测试无问题之后, 将调试成功的代码部署到 /etc/nginx 目录下.

3. 线上试运行

pending...

附: 可选方案

1、商用的通过集成sdk实现

https://www.appadhoc.com

2、开源的需要结合自己公司情况搭建

https://github.com/SinaMSRE/ABTestingGateway

3. 简易配置方案

依据 ip 配置, 结合配置文件

http://jokerzhang.cn/2015/08/13/nginxlua实现灰度发布/

依据 ip 配置, 结合memcache

http://www.cnblogs.com/wenbiao/p/3227998.html

相关文章

添加新评论