灰度发布详细架构图&测试方案
https://www.processon.com/diagraming/5b532fb8e4b053a09c0f7e16
https://www.processon.com/diagraming/5b532471e4b0f8477d8d901c
灰度发布
整体架构
图为整体架构图,分为fe部署块、api部署块、分流策略、升级服务、管理后台
客户端灰度流程
- 浏览器流程
- 客户端流程
- 请求参数
a) 登陆接口增加上传版本
b) 连接websocket增加上传版本和sid(用classId填充)
服务端灰度流程
- 老师端点击上课接口返回灰度信息,如果对应的字段为空表示不需要灰度
- 学生端登陆接口跟增加返回班级列表信息并添加默认选中班级
PC端升级服务
流程如下
分流策略
- 策略管理。
通过后台管理如下功能- 版本管理。包括版本信息、升级地址、升级方案、是否全量发布。其中升级方案包括热更新及官网更新,全量发布以最新的全量版本为准
- 灰度用户管理。班级对应的灰度版本关系维护,如果对应的版本过期则不生效(即配置的灰度版本小于等于当前的最大全量版本)
- 版本灰度策略。管理对应版本的灰度控制,如在哪个实例灰度,灰度时间等
- 服务部署
- 灰度服务器组。每个服务器组管理一个版本,正式服务器组和灰度服务器组等价,每个服务器组内部由nginx根据sid路由
- 版本灰度。在每个服务器组前加一层代理,用来控制版本路由
客户端灰度流程
- 浏览器流程
- 客户端流程
- 请求参数
a) 登陆接口增加上传版本
b) 连接websocket增加上传版本和sid(用classId填充)
服务端灰度流程
- 老师端点击上课接口返回灰度信息,如果对应的字段为空表示不需要灰度
- 学生端登陆接口跟增加返回班级列表信息并添加默认选中班级
PC端升级服务
流程如下
分流策略
- 策略管理。
通过后台管理如下功能- 版本管理。包括版本信息、升级地址、升级方案、是否全量发布。其中升级方案包括热更新及官网更新,全量发布以最新的全量版本为准
- 灰度用户管理。班级对应的灰度版本关系维护,如果对应的版本过期则不生效(即配置的灰度版本小于等于当前的最大全量版本)
- 版本灰度策略。管理对应版本的灰度控制,如在哪个实例灰度,灰度时间等
- 服务部署
- 灰度服务器组。每个服务器组管理一个版本,正式服务器组和灰度服务器组等价,每个服务器组内部由nginx根据sid路由
- 版本灰度。在每个服务器组前加一层代理,用来控制版本路由
管理系统
灰度版本步骤
1.客户端client及服务端server已存在稳定版本A
,现准备发布灰度版本B
,上线步骤:
1.QA部署版本Bserver端
代码至灰度服务器
2.OP后台操作:
- (1)新增版本->填写
版本B
的信息:包括clientUrl, 班级白名单,分流服务ip+port - (2)点击·上线 ·按钮,此时版本B
client端
状态为上线,且ip+port注册到分流服务
2.假设版本B灰度顺利,准备修改为全量上线
1.OP后台操作: 点击版本B的.全量.按钮,此时版本B类型变更为全量,删除白名单
2.QA部署代码至稳定版服务器集群
3.OP后台操作:点击版本B的.上线.按钮,此时删除分流服务中的ip+port映射,默认走default配置,即指向稳定服务器集群
3.假设版本B灰度不顺利,准备下线
1.OP后台操作:点击版本B的下线按钮,此时版本B状态变更下线,删除白名单,删除分流服务ip+port
负载均衡
基本概念
- 传统多机路由方式
包括iphash、轮流访问、随机访问等方式,以达到后端服务负载均衡 - 实时交互系统AIClass
划分最小切割单元,以班级为单位为基础,以用户维度进行路由
AIClass负载均衡架构
如下图
客户端流程
- 老师端
登陆-->班级课程列表–>开始上课–>使用登陆token和classId做为routing key进行长链接建立 -->上课 - 学生端
登陆-->班级列表-->选择班级->使用登陆token和classId做为routing key进行长链接建立 --> 初始化场景
服务路由
- api服务在启动的时候将自己的服务地址注册到nginx内存,即上图的cache,nginx会定时检查健康状态,每5秒检查一次(请求status接口),如果检查失败每秒再检查一次直到成功或则重试三次失败,失败则剔除该注册api服务
- 客户端用路由key请求api服务,请求到nginx后,检查请求中有没有上传路由key字段,没有则走默认后端路由,如果上传则检查cache中有无对应key的服务映射,有映射直接代理到对应服务,否则采用取模hash选择一个有效服务,代理过去并且存储映射到cache
- cache中的映射只保留到晚上凌晨1点
部署步骤
单台服务器部署
- nginx配置步骤
- 服务器组nginx配置,包括正式线上API组,灰度1API组,灰度2API组.....,每个API组对应一套nginx配置以及进程,多个提供API的go服务挂载到某个nginx下为一个API组,组内班级负载均衡策略
- 新建灰度nginx配置目录{dir}
- 将ailua项目下loadbalance目录中的balancer.ngx.conf拷贝到{dir}多份,分别命名为balancer_stable.ngx.conf、balancer_gray_1.ngx.conf、balancer_gray_2.ngx.conf......,其中stable配置一定要有,分别对应一个API组
- 配置ailua路径。将b中的所有配置中的路径“/Users/chenyunnan/apps/lua/ailua”替换为ailua的发布路径,
- 配置日志路径。并配置各个xxx.ngx.conf里的access_log和error_log为不同的路径
- 配置默认API服务。配置中的default_aiclass_upstream里的server配置为supervisor起的aiclassapi的其中一台服务地址,做为默认路由服务器
- 配置server_name和listen。server_name配置为部署机器的内网ip,listen为端口号,b中的每个配置的listen不同
- 到openresty默认配置路径下将nginx.conf拷贝多份,跟2中的xxx.ngx.conf对应,命名为nginx_balancer_stable.conf、nginx_balancer_gray_1.conf、nginx_balancer_gray_2.conf......,
- 将g中conf文件中的include改为对应b中xxx.ngx.conf的绝对路径
- 用openresty -c xxx.conf启动g的各个conf服务
- 流量入口nginx配置,用来接收客户端请求的入口,也是灰度策略的入口,可以部署多个机器做流量负载均衡
- 新建灰度nginx配置目录{dir},可以复用1-a中建立的目录{dir}
- 将ailua项目下grayscale目录中的grayscale.ngx.conf拷贝到{dir},命名为grayscale_1.ngx.conf,grayscale_2.ngx.conf......,可以只复制一个,即单服务模式,配置多个即多机服务模式
- 配置ailua路径。将b中的所有配置中的路径“/Users/chenyunnan/apps/lua/ailua”替换为ailua的发布路径,
- 配置日志路径。并配置各个xxx.ngx.conf里的access_log和error_log为不同的路径
- 配置默认API服务。配置中的default_gray_upstream里的server配置为1步骤配置stable nginx监听的server_name:port
- 配置server_name和listen。server_name为对外提供的域名,端口i为https的端口
- 到openresty默认配置路径下将nginx.conf拷贝多份,命名nginx_grayscale_1.conf,nginx_grayscale_2.conf
- 将g中conf文件中的include改为对应b中xxx.ngx.conf的绝对路径,并在main里增加env AICLASS_GRAYSCALE_MODE=xxx;指定对应环境dev|pre|prod
- 用openresty -c xxx.conf启动g的各个conf服务
- 服务器组nginx配置,包括正式线上API组,灰度1API组,灰度2API组.....,每个API组对应一套nginx配置以及进程,多个提供API的go服务挂载到某个nginx下为一个API组,组内班级负载均衡策略
- GO项目API启动步骤
- 启动一个服务前,配置appconf.toml,新增如下配置,host配置为nginx配置中1所述的nginx server_name:port,意思就是划分到哪个API组
[loadbalance] Host = "127.0.0.1:9100"
-
新增启动命令参数,如./frontlistener -port=8088 -lbs=127.0.0.1:8081,其中port表示监听的端口,lbs为配置的nginx api组
- 启动一个服务前,配置appconf.toml,新增如下配置,host配置为nginx配置中1所述的nginx server_name:port,意思就是划分到哪个API组
多台服务器部署
参照单台服务器配置,只是将nginx分散到不同的服务器,配置对应的server、server_name等参数
配置协议
灰度管理
-
灰度服务器管理
- 添加upstream
- 请求: /grayscale/upstream/update?ups=xxx&&desc=xxxx
- 参数:ups表示灰度的服务实例地址host:port,desc为实例描述名称
- 返回:{"code":0,"message":"success"},code非零表示失败,message为错误信息
- 删除upstream
- 请求: /grayscale/upstream/delete?ups=xxx
- 参数:ups表示灰度的服务实例地址host:port
- 返回:{"code":0,"message":"success"},code非零表示失败,message为错误信息
- 查询upstream
- 请求: /grayscale/upstream/list
- 参数:无
- 返回:{"code":0,"message":"success","data":{"list":[{"ups":"xxxx","desc":"xxxx"}]}},code非零表示失败,message为错误信息,list为当前的服务器列表
- 添加upstream
-
版本映射管理
- 添加版本映射
- 请求: /grayscale/version/update?version=xxxx&ups=xxx
- 参数:ups表示灰度的服务实例地址host:port,version表示要灰度到这个服务器的版本
- 返回:{"code":0,"message":"success"},code非零表示失败,message为错误信息
- 删除版本映射
- 请求: /grayscale/version/delete?version=xxxx
- 参数:version表示要删除的灰度版本
- 返回:{"code":0,"message":"success"},code非零表示失败,message为错误信息
- 查询版本映射
- 请求: /grayscale/version/list
- 参数:无
- 返回:{"code":0,"message":"success","data":{"list":[{"version":"xxxx","ups":"xxxx"}]}},code非零表示失败,message为错误信息,list为所有的版本映射关系
- 添加版本映射
本次针对灰度测试重点如下:
1. 灰度API组的添加、删除、查询(对应提测接口)
2. 灰度版本与API组的映射的添加、删除、查询(对应提测接口)
3. 灰度分流是否正常,在配置了1和2的路由后,对应版本的请求是否正确路由到指定后段API组(可通过API组的日志确认),默认走stable API组流程是否正确(即没有配置任何灰度的时候,是否所有版本都路由到stable API)
4. API组内部(包括stable API)根据班级取模策略路由到不同的API实例机器,比如,如果stable API组有三台API机器服务,classId=100、101和102的班级,上课的时候这三个班级应该会分散路由到三台服务器上
5. 服务器故障摘除稳定性验证,4中的API组入锅摘除非默认实例,正在这个实例上课的班级是否可以重连到另外一台机器正常上课
6. 客户端接入的流量是否均匀分散在不同nginx服务器上
nginx1配置为
grayscaler.ngx.conf 配置内容如下
map $host $lua_dir {
default "/data/www/ailua";
}
lua_package_path "/data/www/ailua/?.lua";
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
map $scheme $upstream_scheme {
default http;
'https' http;
'wss' ws;
}
lua_shared_dict _gray_ups_zone 4m;
lua_shared_dict _gray_ver_zone 4m;
upstream default_gray_upstream {
server nginx1ip:9100;
}
log_format grayscale '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$proxy_host" $upstream_addr $upstream_scheme';
server {
listen 81;
server_name nginx1ip;
rewrite ^(.*) https://aiclass.knowbox.cn$request_uri permanent;
}
server {
listen 80;
server_name nginxip1;
set $site_home_fe /data/www/aiclassfe/dist/final;
server_tokens off;
location ~ ^/grayscale/upstream/([-_a-zA-Z-0-9]+) {
access_by_lua_file $lua_dir/grayscale/access_check.lua;
content_by_lua_file $lua_dir/grayscale/upstream/$1.lua;
}
location ~ ^/grayscale/version/([-_a-zA-Z-0-9]+) {
access_by_lua_file $lua_dir/grayscale/access_check.lua;
content_by_lua_file $lua_dir/grayscale/version/$1.lua;
}
location ~ /api/ {
set $identify '';
set_by_lua_file $ups $lua_dir/grayscale/proxy_version.lua;
proxy_next_upstream off;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass $upstream_scheme://$ups;
proxy_connect_timeout 300s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
rewrite ^/api/(.*)$ /$1 break;
}
location ~ .*\.(js|css|png|jpg)$ {
root $site_home_fe;
expires 1d;
}
location ~ .*\.(html)$ {
root $site_home_fe;
expires 30;
}
location / {
root $site_home_fe;
index student.html;
}
access_log /data/logs/nginx/aiclassapi/aiclass.knowbox.cn_access.log grayscale;
error_log /data/logs/nginx/aiclassapi/aiclass.knowbox.cn_error.log;
}
balancer_stable.ngx.conf配置内容如下
map $host $lua_dir {
default "/data/www/ailua";
}
lua_package_path "/data/www/ailua/?.lua";
init_worker_by_lua_file /data/www/ailua/loadbalance/nginx_init.lua;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
lua_shared_dict _balance_ups_zone 4m;
lua_shared_dict _balance_proxy_zone 128m;
upstream default_aiclass_upstream {
server 10.9.84.249:8100;
}
log_format aiclass '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$proxy_host" $upstream_addr';
server {
listen 9100;
server_name nginxip1;
location ~ ^/loadbalance/upstream/([-_a-zA-Z0-9]+) {
access_by_lua_file $lua_dir/loadbalance/access_check.lua;
content_by_lua_file $lua_dir/loadbalance/upstream/$1.lua;
}
location / {
set $identify '';
set_by_lua_file $ups $lua_dir/loadbalance/proxy_ups.lua;
proxy_next_upstream off;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass $scheme://$ups;
}
access_log /data/logs/nginx/aiclassapi/aiclassapi_stable_access.log aiclass;
error_log /data/logs/nginx/aiclassapi/aiclassapi_stable_error.log;
}
nginx2配置
balancer_gray.ngx.conf 配置内容
map $host $lua_dir {
default "/data/www/ailua";
}
lua_package_path "/data/www/ailua/?.lua";
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
map $scheme $upstream_scheme {
default http;
'https' http;
'wss' ws;
}
lua_shared_dict _gray_ups_zone 4m;
lua_shared_dict _gray_ver_zone 4m;
upstream default_gray_upstream {
server ngixnip1:9100;
}
log_format grayscale '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$proxy_host" $upstream_addr $upstream_scheme';
server {
listen 81;
server_name nginxip2;
rewrite ^(.*) https://aiclass.knowbox.cn$request_uri permanent;
}
server {
listen 80;
server_name nginx2;
set $site_home_fe /data/www/aiclassfe/dist/final;
server_tokens off;
location ~ ^/grayscale/upstream/([-_a-zA-Z-0-9]+) {
access_by_lua_file $lua_dir/grayscale/access_check.lua;
content_by_lua_file $lua_dir/grayscale/upstream/$1.lua;
}
location ~ ^/grayscale/version/([-_a-zA-Z-0-9]+) {
access_by_lua_file $lua_dir/grayscale/access_check.lua;
content_by_lua_file $lua_dir/grayscale/version/$1.lua;
}
location ~ /api/ {
set $identify '';
set_by_lua_file $ups $lua_dir/grayscale/proxy_version.lua;
proxy_next_upstream off;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass $upstream_scheme://$ups;
proxy_connect_timeout 300s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
rewrite ^/api/(.*)$ /$1 break;
}
location ~ .*\.(js|css|png|jpg)$ {
root $site_home_fe;
expires 1d;
}
location ~ .*\.(html)$ {
root $site_home_fe;
expires 30;
}
location / {
root $site_home_fe;
index student.html;
}
access_log /data/logs/nginx/aiclassapi/aiclass.knowbox.cn_access.log grayscale;
error_log /data/logs/nginx/aiclassapi/aiclass.knowbox.cn_error.log;
}
grayscaler.ngx.conf 配置内容
map $host $lua_dir {
default "/data/www/ailua";
}
lua_package_path "/data/www/ailua/?.lua";
init_worker_by_lua_file /data/www/ailua/loadbalance/nginx_init.lua;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
lua_shared_dict _balance_ups_zone 4m;
lua_shared_dict _balance_proxy_zone 128m;
upstream default_aiclass_upstream {
server grayip_online:8200;
}
log_format aiclass '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$proxy_host" $upstream_addr';
server {
listen 9200;
server_name nginx2;
location ~ ^/loadbalance/upstream/([-_a-zA-Z0-9]+) {
access_by_lua_file $lua_dir/loadbalance/access_check.lua;
content_by_lua_file $lua_dir/loadbalance/upstream/$1.lua;
}
location / {
set $identify '';
set_by_lua_file $ups $lua_dir/loadbalance/proxy_ups.lua;
proxy_next_upstream off;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass $scheme://$ups;
}
access_log /data/logs/nginx/aiclassapi/aiclassapi_gray_access.log aiclass;
error_log /data/logs/nginx/aiclassapi/aiclassapi_gray_error.log;
}
grayscaler用于管理
grayscale有2台,当客户端发起请求的时候,通过version,进行不同的机器集群映射。
grayscale就是记录version=>集群的映射关系。
grayscale就是管理一个版本,比如1.5.0,1.5.1是映射到stable,还是gray