灰度发布系统简介

灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式。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

相关文章

  • nginx 性能优化配置
    http://www.oschina.net/translate/nginx-setup
  • nginx accept_mutex
    http://huoding.com/2013/08/24/281

正常情况下,我们写入的iptables规则将会在系统重启时消失。即使我们使用iptables-save命令将iptables规则存储到文件,在系统重启后也需要执行iptables-restore操作来恢复原有规则。(当然,你也可以通过在network中的if.post.up.d中配置启动规则来达到开机自动启动iptables的方法)

这里我们有一个更好的iptables持久化方案,让防火墙规则重启后依旧有效。即使用iptables-persistent工具。

首先,安装:

sudo apt-get install iptables-persistent

安装完后即可使用以下命令保存或载入规则:

Ubuntu 14.04

sudo invoke-rc.d iptables-persistent save
sudo invoke-rc.d iptables-persistent reload

或者

sudo /etc/init.d/iptables-persistent save 
sudo /etc/init.d/iptables-persistent reload

Ubuntu 16.04

sudo netfilter-persistent save
sudo netfilter-persistent reload

生成的规则将被存储在以下文件中

/etc/iptables/rules.v4
/etc/iptables/rules.v6

参考:How to save rules of the iptables?

由官方文档直接翻译的,非常不错,贴一个链接,抽空在整理下:

link : https://www.zouyesheng.com/imagemagick.html

某文件夹下有很多文件, 将文件夹中的文件拆分到多个目录中

cd file_path
ls | head -20000 | xargs -i {} mv {} /new_path

说明: xargs -i 是将输出按照行为单位输出给后面的命令, 例如:

find ./logs -name "*log" | xargs -i echo {}

说明: xargs -p 逐条确认执行,每次输出,都会让你手动确认下是否执行,y 执行, n 不执行,例如:

find ./logs -name "*log" | xargs -p -i echo {}

用 xargs file 输出文件类型

find . -type f -print | xargs file

文件批量修改后缀

rename 's/\.jpg\.jpeg/g'

找出不符合命名规则的文件

find -type f -print |grep "jpg" | grep -vE "[A-Za-z0-9_\/ ]+\([0-9]+\).jpg" |xargs -i cp {} /data/doit

说明: 在 shell 下使用 grep 空格 直接使用空格就好, \s 貌似不好使 , \d 貌似也不好使, 直接使用 [0-9], 是不是真这样呢?

批量压缩图片

进入目录后进行转换, 用 find 命令配合 xargs 会报错,但是在目录下操作就没问题

ls | xargs -i convert -resize 1200 -quality 95% {} ../new path/{}

批量整理文件名

很多文件, 文件名类似 name_1.jpg, name_2.jpg, 需要整理出不重复的 name. 文件名比较有规律, 所以用 substr 方法截取.
ls -l | xargs -i expr substr {} 1 9 | sort -u > 1.txt

redis 根据条件清理 key

通过 pattern 匹配

redis-cli -a password --scan --pattern '*history*' | xargs redis-cli -a password del {}

通过 shell 匹配

#! /bin/bash

if [ ${#1} -eq 56 ]
then
     redis-cli -a [email protected] del $1
fi

redis-cli -a --scan --pattern 'history' | xargs -i /root/check.sh {}


#### 创建 swap 交换磁盘

dd if=/dev/zero of=swap.disk bs=1M count=3k && mkswap -f swap.disk && swapon swap.disk

有时候,你会莫名奇妙的遇到 mysql 不能登录的场景,也有可能是你忘记了你的 root 密码, 这时候怎么办呢?

首先,你需要停止你的 mysql 服务 然后用 safe 模式下 skip the grant tables 方式运行, 在安全模式下, 更新下你的密码.

过程如下:

service mysqld stop
mysqld_safe --skip-grant-tables
mysql -u root -p

此时无需密码即可登录,接着就可以修改密码了:

use mysql;
update user set Password=PASSWORD('new-password') where user='root';
flush privileges;
exit

此时, kill mysql_safe 进程, 重新启动mysql, 在尝试一下用新密码登录吧。