灰度发布GrayRelease/Dark launch详解

灰度发布GrayRelease/Dark launch详解最近项目需求,研究了一下灰度发布,网上查了查,这里记录一下。什么是灰度发布灰度发布(又名金丝雀发布,英文一般称为GrayRelease或Darklaunch)是为了能够让用户逐步过渡到新功能一种发布方式。一般是产品上线一个功能,希望在线上可以进行A/Btesting,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范…_灰度发布英文

灰度发布GrayRelease/Dark

最近项目需求,研究了一下灰度发布,网上查了查,这里记录一下。

什么是灰度发布

       灰度发布(又名金丝雀发布,英文一般称为GrayRelease或Dark launch)是为了能够让用户逐步过渡到新功能一种发布方式。 一般是产品上线一个功能,希望在线上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。

优点

  • 灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。
  • 灰度发布可以让部分用户尽快体验到最新的功能,提高用户的积极性,进而收集用户反馈改善产品。

    A/B test测试在Wikipedia中定义是Web设计(通常指用户体验)中用于区分两种网页设计对收益最大化目标(如点击率)效果支撑程度的一种试验手段”。主要用于比较两种设计的优劣程度。

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

开源灰度发布系统

https://github.com/Kong/kong
https://github.com/sumory/orange
https://github.com/CNSRE/ABTestingGateway

 

为什么要灰度发布

  • 互联网服务变动频繁,发布周期短。速度与质量总是难以双全。
  • 灰度发布能降低发布风险,减少影响范围。
  • 降低对测试的依赖,减少线下自测的数据构造成本。
  • 方便集中监控日志,全量发布由于各层负载均衡的作用,很难跟踪一条完整的调用链路。
  • 可以灰度测试帐号,测试账户通过之后再灰度真实用户帐号,进一步降低发布的风险和影响。
  • 方便回滚。

不能靠灰度发布解决的问题

需要强调的是:上文所说的“可以容忍的影响”必须是可恢复的,比如API无法调用一段时间,但是修复之后,就可以成功调用。而永久性地丢失或者破坏用户数据(比如商品信息、订单信息等),则是不能容忍的。因此,互联网企业的架构师有责任通过设计完善的后备措施(比如用户数据的定期备份、写操作的业务流水日志等),在生产系统错乱导致丢失用户数据的情况下,仍能够通过人工干预,根据历史记录(备份数据、流水日志等),把丢失的用户数据修复至不久之前(比如一小时前至一周前)的状态。

TIPS 先灰度测试帐号的灰度策略,可以降低破坏或者丢失真实用户的数据的风险。

期望达到什么效果

不管是那种变更,我们都希望特定的请求能够路由到我们的变更版本(灰度版本),以便观察和验证。

灰度策略

其实就是什么的请求应该路由到我们的灰度版本(灰度机器)上来。这个往往是业务强相关的。比如对于API来说,一般有如下几个需求:

  • 特定用户(比如测试帐号)
  • 特定的App(比如测试app或者合作App)
  • 特定的模块、接口(只有某些接口需要灰度,这种一般是API Container的修改,拿一些不是很重要的API做灰度测试。)
  • 特定的机器(某些请求IP转发到灰度机)

灰度方案探讨

方案一 代码级别通过对约定好的flag判断,动态的进行新老切换——Amazon的做法

实现: 
在代码中埋开关,做if-else判断,对于需要灰度的机器,设置开关为on,否则为off。每次版本发布都是有两个版本。

优点 
快速回滚,不需要重新发布和重启系统。

缺点 
对代码有侵入。 
分支逻辑,带来复杂性。

方案二 预发布机——Alibaba的做法

其实这个不是真正意义上的灰度。因为这个预先发布机器是内部IP,没有对外服务的。需要绑定域名进行验证。但是数据是完全的线上。所以本质上是灰度某些特定用户(可以访问灰度机器的用户,内部测试用户)的一种简单做法。其实API这边也有类似的做法,就是我们的Gamma环境,而且我们还提供了Gamma机器的域名,方便外部合作用户配合测试。

优点 
简单

缺点 
– 浪费一台机器(这个可以预先发布完成之后投入正式环境,预发布的时候从nginx摘除,不过需要运维支持。) 
– 不够灵活 
只能针对接入层机器,IDL服务灰度需要另外考虑。

方案三 SET部署

按照业务隔离部署

比如现在API Container的做法,部署的粒度可以到API级别,前端根据nginx进行转发。比如: 
– 微购物 API Container: api.weigou.qq.com 
– 拍拍 API Container:api.paipai.com 
– 易迅 API Container: api.yixun.com 
– 网购 API Container:api.buy.qq.com

上面是大业务级别的隔离部署。还可以进一步细化到模块级别,比如虚拟服务电商的API,是挂在拍拍下面的一个子业务模块,但是由于他们接入微信之后,访问量大增,为了避免影响拍拍其他业务,也为了避免受其他业务影响,API这里是给他们单独部署了两台机器,nginx配置一下就可以将针对虚拟的API访问引流过来了:

虚拟API Container:http://api.paipai.com/v2/virbiz

这样,我们在发布一个版本的时候,可以先选择业务量最小的易迅进行发布,观察没有问题再全量其他平台。

按照用户隔离部署

这个对于开放平台来说不是很适合,不过对于SNS这种应用场景就很合适了。比如QQ系统,按照用户号码段分为若干个set,每个set包含连续1亿个号码的用户。假设现在最新的QQ号码接近10亿,则总共有10个set(Set 1到Set 10)。这样每次可以选择其中一个SET进行发布,而且高位QQ往往是不是很重要的用户,所以会先发布SET10。

优点 
隔离部署,各个业务线影响最小。自动支持灰度发布。

缺点

  • 灰度的粒度取决于隔离部署的粒度,一般会偏大。
  • 相对于集中部署比较浪费机器。
  • 各个业务线版本可能不一致,不利于统一管理。
  • 有一定的实现和部署成本

方案四 动态路由

方法:采用一个可以灵活配置的灰度策略,影响Load Balance的行为,让其根据灰度策略,返回灰度服务的IP和端口。

优点 
灵活,可控。

缺点 
– 现在的配置中心和L5本身没有考虑指定路由策略,且不具有扩展性,需要在其外边开发。 
– API的元数据来源比较分散,目前 API和IDL元数据,API等级和频率限制 分布在不同的数据源,现在需要增加一个 灰度路由 数据源。

最终方案

  • API Container采用预发布机模式灰度

  • IDL服务采用动态路由模式,不过只能支持uin或者IP来源。因为没有appId的概念。

 

Nginx实战 之 灰度发布

    灰度发布在谷歌和Facebook等很多公司已经使用的相当成熟,具体的分流规则也有很多,下面简单介绍下几种常见的分流规则,并分别使用nginx来配置实现。

基于COOKIE分流

      使用Cookie分流的原理为:在用户首次登录时查询该用户是否是灰度用户,并为其设置标识Cookie,后续采用Cookie标识来进行分流。
      本文采用dark字段作为标识,如果cookie中dark的值true则分流到灰度环境,其他情况则分流到生产环境。
在nginx的原生变量中,$http_cookie的值为所有Cookie以key=value的形式拼接,而$cookie_dark则只获得Cookie中dark的值。所以可以采用两种方式来判断:

  1. 使用$http_cookie,则里面需要包含dark=true
  2. 使用$cookie_dark,则其值需要为true

最终nginx.conf文件中的核心配置如下:

pstream normal {

    server 127.0.0.1:8080;

}

upstream dark {

    server 127.0.0.1:8081;

}

server {

    listen 80;

    server_name localhost;

    set $group normal;

    #或者使用 if ($cookie_dark ~* “^true&”) 来判断

    if ($http_cookie ~* “dark=true”){

        set $group dark;

    }

    location / {

        proxy_pass http://$group;

    }

}

 

注意 nginx使用的正则规则如下:

~  表示区分大小写的正则匹配
~* 表示不区分大小写的正则匹配
^  表示以xxx结尾
$  表示以xxx结尾

      分流效果演示:启动了两台Tomcat,其中8080端口作为生产环境,8081作为灰度环境,为了加以区分,我在灰度环境的index.jsp页面上加上了This is dark launch page!。使用Postman来模拟请求,大家都知道POSTMAN能够添加Header,但是却不能修改Cookie,这时候需要Postman Interceptor来救场了。可以看到下载并启用Postman Interceptor插件以后,在Header中直接编辑Cookie属性,即可修改请求的Cookie。

    使用postman-interceptor修改cookie
    可以看到带有灰度Cookie的分流到了灰度环境。
    另外为了调试方便,可以直接access.log加上$http_cookie$cookie_dark

log_format main ‘dark=$cookie_dark – $remote_addr – $remote_user [$time_local] “$request” ‘

‘$status $body_bytes_sent “$http_referer” ‘

‘”$http_user_agent” “$http_x_forwarded_for”‘;

access_log logs/access.log main;

请求产生的日志打印如下:

dark=true – 127.0.0.1 – – [04/Nov/2017:14:08:46 +0800] “GET / HTTP/1.1” 200 11452 “-” “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36” “-“

基于Header分流

基于Header分流的原理和用法都和Cookie类似。只有两点不同:

  1. 获取灰度标识,使用的是$http_dark(即$http_header名)的写法;
  2. Header不能像Cookie一样在客户端保留,所以一般是在多层网络中使用,即入口层给Header赋值,分流层再去分流。

这里为了演示ngx_http_map_module的用法,使用了map替代了上面的set if判断方法。

map $http_dark $group {
    ~*true$ dark;
    default normal;
}

server {
    listen 80;
    server_name localhost;
    location / {
        proxy_pass http://$group;
    }
}

基于IP分流

基于IP分流原理:校验客户端的IP是否在我们的灰度IP列表中。
所以关键有两点:

  1. 如何获取客户端IP?其实有很多方式。可以参考这两篇博客nginx 如何配置来获取用户真实IP 和 HTTP 请求头中的 X-Forwarded-For,X-Real-IP
  2. 灰度IP列表怎么保存?其实可以保存在配置文件或数据库等任意地方。

    假设我们有一张mysql的表存着配置的所有需要分发到灰度的IP地址,如果请求的IP在表中能够查到的话,则分流到灰度环境。我们这边最简单的创建了一个表,并插入一条IP地址。

CREATE TABLE `test`.`dark_launch_ips`( `ip` VARCHAR(16) NOT NULL, PRIMARY KEY (`ip`) );
INSERT INTO `test`.`dark_launch_ips` (`ip`) VALUES ('1.1.1.1');

     本节使用了lua脚本来判断分流,在Nginx里面使用lua需要Nginx额外增加lua-nginx-module模块,或者直接使用打包好的OpenResty。为了演示,我们依然使用了两个location,只是不再使用upstrem,而是使用content_by_lua简单返回一个字符串,目的是能够在区分分流的前提下尽量简化其他细节,以突出核心逻辑。在Nginx中连接mysql的方法参考lua-resty-mysql模块。最终Nginx的核心配置如下:

location @normal {

content_by_lua ‘ngx.say(“normal”)’;

}

location @dark {

content_by_lua ‘ngx.say(“dark”)’;

}

location /test {

access_by_lua ‘

local mysql = require “resty.mysql”

local db, err = mysql:new()

if not db then

ngx.log(ngx.ERR, “failed to instantiate mysql: “, err)

ngx.exec(“@normal”)

end

local ok, err, errcode, sqlstate = db:connect {

host = “127.0.0.1”,

port = 3306,

database = “test”,

user = “root”,

password = “root”,

charset = “utf8”,

max_packet_size = 1024 * 1024,

}

if not ok then

ngx.log(ngx.ERR, “failed to connect: “, err, “: “, errcode, ” “, sqlstate)

ngx.exec(“@normal”)

end

— 以上代码是连接数据库操作,下面代码是获取IP并去数据库查询验证

local req_ip = ngx.var.http_x_real_ip or ngx.var.http_x_forwarded_for or ngx.var.remote_addr or “0.0.0.0”

local name = ngx.unescape_uri(req_ip)

local quoted_name = ngx.quote_sql_str(name) — 防SQL注入

local res, err, errcode, sqlstate = db:query(“SELECT COUNT(*) AS cnt FROM dark_launch_ips WHERE ip = ” .. quoted_name)

if not res then

ngx.log(ngx.ERR, “bad result: “, err, “: “, errcode, “: “, sqlstate, “.”)

ngx.exec(“@normal”)

end

if tonumber(res[1][“cnt”]) > 0 then

ngx.exec(“@dark”)

end

ngx.exec(“@normal”)

‘;

}

注意:

  • 从Header中取出参数值可以通过ngx.var.http_x_real_ipngx.req.get_headers()["X-Real-IP"],参考ngx.var.VARIABLE
  • 利用ngx.var.http_x_real_ip or ngx.var.http_x_forwarded_for or ngx.var.remote_addr or "0.0.0.0"获取IP,仅仅是一种使用方式,具体的IP获取方式需要根据实际使用场景来决定[例如是否有多层代理]。
  • ngx.quote_sql_str(name) 可以防SQL注入,详情参考文档lua-resty-mysql,如果不转义直接拼接,攻击者可以伪造Header来跨过校验。例如采用下面代码自行拼接SQL,则使用Header值为1.1.1.0’ or ‘1’=’1即可通过校验

    “SELECT COUNT(*) AS cnt FROM dark_launch_ips WHERE ip = \'” .. req_ip .. “\'”;

  • db.query查询出的结果res是一个二维数组,可以通过以下当代查看其结构,本例中的结果为[{“cnt”:”1”}],具体可以参考Debugging

    local cjson = require "cjson"
    local result = cjson.encode(res)
    ngx.log(ngx.INFO,"result: ", result)
  • 如果为了提高性能,可以采用init_by_lua将IP一次从数据库中取出放在全局变量中,在access_by_lua时使用全局变量判断即可。这样可以解决频繁访问数据库的问题,但是如果中间有修改(或增加删除)数据库中的IP,则可能不会立即生效,而且配置IP太多,容易占用很大内存。当然新引入的两个问题也有解决方案,比如修改IP后,触发一次Nginx的reload和采用更好的算法来解决内存问题。大家只需参考,实际使用还需根据自己的场景选择。

  • lua-resty-mysql模块还提供了连接池效果的功能。详情可以参考文档set_keepalive函数

其他分流

      其他分流策略还有很多,如根据请求URL分流、随机分流、根据请求字段(如客户ID)的hash值分流等,一般都是与业务相关。
下面再介绍下URL分流,例如以下场景:如果url以dark结尾,则分流到灰度。本文继续使用一个新的模块ngx.balancer来做负载均衡。

 

upstream balancer {

server 0.0.0.1; # 这里写一个不存在的IP,作为占位填充

balancer_by_lua_block {

local balancer = require “ngx.balancer”

— 下面一般是根据某些参数来计算要分流到哪里

local host = “127.0.0.1”

local port = 8080

local m, err = ngx.re.match(ngx.var.uri, “dark$”)

if m then

port = 8081

end

local ok, err = balancer.set_current_peer(host, port)

if not ok then

ngx.log(ngx.ERR, “failed to set the current peer: “, err)

return ngx.exit(500)

end

}

keepalive 10; # connection pool

}

     访问http://localhost/someurl-dark 访问的是灰度节点;访问http://localhost/someurl 访问的是生产节点。
访问验证正如预期!

 

参考:

https://blog.csdn.net/whereismatrix/article/details/53239198
https://vther.github.io/nginx-dark-launch/
https://chai2010.cn/advanced-go-programming-book/ch5-web/ch5-09-gated-launch.html
https://segmentfault.com/a/1190000017894943
http://www.appadhoc.com/blog/what-is-gray-release/
http://www.saily.top/2019/02/28/spring-cloud-nepxion-gray/
http://rock-op.github.io/blog//2016/11/16/our-gray-release/
https://zhangll123.iteye.com/blog/2370747

 

今天的文章灰度发布GrayRelease/Dark launch详解分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/40163.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注