灰度发布方案
简要架构设计框图
灰度发布划分
Web前端灰度
假设我们的前端资源存放在staticServer上:我们每次发布一个新版本,就把资源增量式地上传到staticServer,然后给它分配一个唯一的版本号,再把所有的版本号存储起来。当处理请求时,根据动态配置的分流策略来决定用户使用哪个版本。
比如分流策略是放量10%,即新版本随机放量给10%的用户使用,当用户首次命中资源版本号时,需要把用户id和版本号的映射关系存储起来(可存到cookie),这样就能保证同个用户上次请求和下次请求访问到的都是同个版本的代码。
那如果线上有紧急bug需要修复,又要重新发布新版本,该如何处理当前灰度的状态?是赶紧结束上一个灰度然后全量发布还是一起发上去同时灰度?一般来说,再有新版本发布或者放量策略发生变化时,应该重新分流灰度。
客户端灰度
web前端和服务端灰度发布可以在客户无感知的情况下平滑进行,遇到问题也可以快速回滚,但是移动客户端涉及到用户的主动安装行为,所以上述的方式已经不适用。
如果一个带有bug的安装包全量发布出去,一旦有问题,我们只能快速定位问题来提醒用户安装新版本,是否安装取决于用户,所以客户端灰度发布是非常有必要的。
客户端在启动时,会向灰度系统发起请求,灰度系统根据客户端传过来的参数和当前的放量策略来决定是否要给客户端升级提醒。一般会根据以下几种策略来决定给予用户升级提醒:
[if !supportLists]1. [endif]根据用户设备的系统和应用版本;
[if !supportLists]2. [endif]根据渠道:发布到不同应用市场的app都会打上渠道标签,所以可以根据渠道来区分用户;
[if !supportLists]3. [endif]根据设备ID和用户ID,通过设备ID主要是为了控制提醒频率,用户ID主要是为了区分出特性用户,比如对活跃用户发送提醒。
服务端的灰度
兼容变更
兼容变更又可以分为物理灰度和逻辑灰度
[if !supportLists]l [endif]物理灰度:物理灰度比较简单,根据机器维度进行灰度,直接部署新老版本在不同机器,流量均匀地打在新老版本上面。这种方式虽然简单,但不适用于不兼容变更;
[if !supportLists]l [endif]逻辑灰度:逻辑灰度就是根据更精确的流量策略来控制流量,这种灰度一般要写一定的灰度代码。这种方式能比较精确地控制流量,但是增加了一定的灰度代码,灰度完成后要删除相关灰度代码,有点麻烦。
不兼容变更
不兼容变更指的是更改了当前功能,即接口逻辑跟之前版本发生很大变化,必须要前后端同时发布,否则会有一段时间服务不可用。
一般的做法是引入接口版本号,新老版本接口并存,比如/v1/api 和 /v2/api。前端使用/v2/api版本,当过去一段稳定期后(可以是登录态时间失效后),就可下掉/v1/api版本。
灰度放量策略
[if !supportLists]1. [endif]按流量百分比
先到先得的方式比如限制10%的用户体验的是新版本,90%的用户体验的是老版本。先访问网站的用户就优先命中新版本,直到流量用完为止。
[if !supportLists]2. [endif]按人群划分
[if !supportLists]l [endif]按用户id、用户ip、设备类型比如可通过平时的埋点上报数据得知用户的pv、uv、页面平均访问时长等数据,根据用户活跃度来让用户优先体验新版本,进而快速观察使用效果。
[if !supportLists]l [endif]按地域、性别、年龄等用户画像比如可通过用户的性别、年龄等做下新老版本的对比效果来看看目标用户在新版本的使用年龄段,性别范围是多少。
[if !supportLists]3. [endif]按渠道划分
比如根据用户的注册来源来放量。
具体实施
运维角度考虑:
[if !supportLists]1. [endif]利用nginx配置负载控制
[if !supportLists]2. [endif]利用http头信息判断+权重(灰度值)
http请求传输过程中,会自动带上User-Agent,Host,Referer,Cookie等信息。我们需要分析ip地址段,用户代理,Cookie中的信息。根据Cookie查询version值,如果该version值为v1转发到host1,为v2转发到host2,都不匹配的情况下转发到默认配置。
[if !supportLists]3. [endif]使用灰度发布工具
开发角度考虑:(二选一)
版本控制:通过切换不同的分支来选择上线版本并制定打版回退策略
代码开关:
一套代码,通过代码开关控制新功能上线与否。利用代码中的功能开关来控制发布逻辑,一般不需要复杂的发布工具,是一种相对比较低成本和简单的发布方式。研发人员可以灵活定制和自助完成的发布方式。实现过程如下:
[if !supportLists]l [endif]功能开关发布需要一个配置中心,通过配置中心,运维或研发人员可以在运行期动态配置功能开关的值。
[if !supportLists]l [endif]新功能和老功能在同一套代码中,新功能隐藏在开关后面,如果开关没有打开,则走老代码逻辑,如果开关打开,则走新代码逻辑。
[if !supportLists]l [endif]应用上线后,开关先不打开,然后运维或研发人员通过配置中心打开新功能,经过流量验证新功能没有问题,则发布完成;如果有问题,则随时可以通过配置中心切回老功能逻辑。
运营角度考虑:
监控新功能使用情况,是否使用频繁,用户满意度的监控,带来了新用户?导致老用户卸载?
测试角度考虑:
除了正常功能以外,还要确保每一台服务器上的应用功能都是可用的(灰度时验证,全部上线时还需验证)
灰度发布具体流程:
灰度发布流程细节如下:
[if !supportLists]1. [endif]在灰度发布开始后,先启动一个新版本应用,但是并不直接将流量切过来,而是测试人员对新版本进行线上测试,启动的这个新版本应用,就是我们的金丝雀。
[if !supportLists]2. [endif]如果测试没有问题,那么可以将少量的用户流量导入到新版本上,然后再对新版本做运行状态观察,收集各种运行时数据,如果此时对新旧版本做各种数据对比,就是所谓的A/B测试。
[if !supportLists]3. [endif]当确认新版本运行良好后,再逐步将更多的流量导入到新版本上,在此期间,还可以不断地调整新旧两个版本的运行的服务器副本数量,以使得新版本能够承受越来越大的流量压力。直到将100%的流量都切换到新版本上,最后关闭剩下的老版本服务,完成灰度发布。如果在灰度发布过程中发现了新版本有问题,就应该立即将流量切回老版本上,这样,就会将负面影响控制在最小范围内。
基于Openresty实现的灰度发布技术方案
系统功能设计
本系统是基于Openresty+Redis+Lua方式实现。OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项,用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
本系统实现了对Http协议和Tcp协议的灰度功能,Http协议主要基于ngx_http_lua_module模块实现,Tcp协议主要基于ngx_stream_lua_module模块实现。
功能概述
本系统实现了对Http协议和Tcp协议的灰度功能,并且提供后台管理系统对灰度白名单进行管理。
Http协议灰度实现如下功能:
[if !supportLists]1. [endif]通过IP或者IP段验证客户端IP是否灰度白名单;
[if !supportLists]2. [endif]通过获取header头中的userid,验证是否灰度白名单;
[if !supportLists]3. [endif]通过获取header头中的device-id,验证是否灰度白名单。
TCP协议灰度实现如下功能:
通过IP或者IP段验证客户端IP是否灰度白名单;
后台管理系统实现如下功能:
[if !supportLists]1. [endif]支持对设备deviceId灰度白名单新增、删除和查询功能;
[if !supportLists]2. [endif]支持对客户端IP灰度白名单新增、删除和查询功能;
[if !supportLists]3. [endif]支持对用户userId灰度白名单新增、删除和查询功能,同时可以根据配置灰度策略,将满足条件的用户自动导入灰度白名单中。
业务流程
说明:
[if !supportLists]1. [endif]当用户请求到达前端web(代理)服务器Openresty,内嵌的lua模块解析Nginx配置文件中的lua脚本代码;
[if !supportLists]2. [endif]Lua获取客户端IP地址、userId和设备ID,去查询Redis中是否有该键值,如果有则转发到灰度环境,否则转发到生产环境中;
[if !supportLists]3. [endif]后端管理系统提供可视化界面,可以管理redis中的白名单信息,包括:客户端IP白名单、userId白名单、设备Id白名单。
Http协议灰度实现
实现原理
在Openresty中通过ngx_http_lua_module模块的指令access_by_lua_file,解析指定的lua脚本,通过lua脚本获取客户端的IP地址,请求header头中的userId和设备ID,将以上信息在Redis中做比对,如果存在则代理跳转到灰度环境,否则代理跳转到生产环境中。
流程图如下图所示:
TCP协议灰度实现
实现原理
在Openresty中通过ngx_stream_lua_module模块的指令preread_by_lua_file,解析指定的lua脚本,通过lua脚本获取客户端的IP地址,将IP地址在Redis中做比对,如果存在则代理跳转到灰度环境,否则代理跳转到生产环境中。
如果TCP通信过程中涉及到多层代理,则需要通过使用ngx_stream_realip_module模块来获取原始客户端IP,ngx_stream_realip_module模块编译安装时默认未构建此模块,启用此模块需要在编译安装时使用--with-stream_realip_module 配置参数启用它 ,
nginx.conf配置示例:
[if !supportLists]l [endif]转发中间层:
upstream foo_test_3 {
server 127.0.0.1:8061;
}
server {
listen 8064;
proxy_protocol on;
proxy_pass foo_test_3;
proxy_timeout 120s;
}
[if !supportLists]l [endif]转发最后层:
upstream foo_test_2 {
server 127.0.0.1:8063;
}
server {
listen 8061 proxy_protocol;
set_real_ip_from 192.168.0.5;
proxy_pass foo_test_2;
proxy_timeout 120s;
}
流程图如下图所示:
后台管理系统
1)IP白名单管理
[if !supportLists]n [endif]后台管理系统支持对客户端IP灰度白名单新增、删除和查询功能
2)用户白名单管理
[if !supportLists]n [endif]后台管理系统支持对用户userId灰度白名单新增、删除和查询功能,同时可以根据配置灰度策略,将满足条件的用户自动导入灰度白名单中。
3)灰度策略管理
[if !supportLists]n [endif]后台管理系统支持对灰度策略新增、删除、禁用和查询功能。
[if !supportLists]n [endif]新增灰度策略支持如下几种策略类型:手机号全匹配、手机号开头数字匹配、手机号结尾数字匹配、用户userId哈希取模匹配、用户高频次交易匹配。