假如咱们有一组后端服务为用户提供某些功能,各服务之间的调用关系(简化的)可能以下图前端
在实际的工做中,咱们常常遇到这种场景:同时有多个feature在开发,好比feature_x,feature_y,feature_z,json
这些feature的代码改动可能涉及到若干个相同或者不一样的的后端服务,例如:后端
1)feature_x修改了app_srv0的<用户帐户展现> 接口(假设该接口的名字是user_account_show),user_account_show接口依赖app_srv1的 <帐户余额接口>user_account_balanceapp
2)feature_y特性修改了user_account_balance接口,这个修改承诺接口的对外输出不改变,只是修改了用户的帐户余额计算公式。框架
在feature_y测试经过以前”user_account_balance接口对外输出不改变“这个承诺是没法获得保障的,若是咱们只有一个测试环境,多个feature都只能在这个测试环境上进行测试,那么feature_x,feature_y的测试只能串行。若是并行进行测试的话,服务部署会是下面这个样子的(浅绿表示feature_x,紫色表示feature_y):性能
测试过程当中若是feature_y有bug致使user_account_balance返回的数据不对,就会间接致使user_account_show接口返回的展现信息也不对,从而致使测试认为。但实际feature_x的修改。测试
1)测试串行,缺点:时间周期常,项目feature上线慢spa
2)并行部署多套测试环境,缺点:资源成本和维护成本都很高,若是服务众多或者并行开发的版本多那么基本不具有可执行性3d
3)使用一套环境强行并行测试,缺点:特性之间的相互影响可能致使多个特性的测试都须要返工,拖长各个feature测试的耗时code
先不废话,上个图
由上面的分析咱们能够知道要考虑的是“怎么已较小的代价实现测试环境特性隔离”,“代价小”也就是尽量少的部署服务,只部署有修改的服务便可;“特性隔离”就是不一样特性分支的请求分别路由到支持对应特性的服务版本上。参考上图:
feature_x修改了app_srv1,app_srv4,app_srv6,分别获得新版本服务app_srv1_x,app_srv4_x,app_srv6_x
feature_y修改了app_srv4,app_srv7,分别获得新版本服务app_srv4_y,app_srv7_y
假设feature_x版本的请求为Rx,feature_y版本的请求为Ry,基线版本的请求为R0,只要作到Rx,Ry请求分别走不一样的调用链便可保证feature_x和feature_y的测试相互不影响,也就是:
Rx的调用链:app_srv0 —> app_srv1_x —> app_srv2 —> app_srv4_x —> app_srv6_x —> app_srv7
Ry的调用链:app_srv0 —> app_srv1 —> app_srv2 —> app_srv4_y —> app_srv6 —> app_srv7_y
让请求携带路由信息(如下格式为非标准json格式,只是相似格式方便展现而已)
在请求的公共头部携带一个版本绑定的路由信息,好比编号feature_x_20180101,feature_y_20180101
//feature_x
//comm_head
{
"route_id":"feature_x_20180101"
}
//feature_y
//comm_head
{
"route_id:feature_y_20180101"
}
复制代码
存储一张路由表
//route_table
{
"feature_x_20180101": [
{
"appid": "app_srv1",
"srv_version": "app_srv1_xxx",
"addr": "ip:port"
},
{
"appid": "app_srv4",
"srv_version": "app_srv4_xxx",
"addr": "ip:port"
},
{
"appid": "app_srv1",
"srv_version": "app_srv6_xxx",
"addr": "ip:port"
}
],
"feature_y_20180101": [
{
"appid": "app_srv4",
"srv_version": "app_srv4_yyy",
"addr": "ip:port"
},
{
"appid": "app_srv7",
"srv_version": "app_srv7_yyy",
"addr": "ip:port"
}
]
}
复制代码
每一个服务维护本身的appid,发起rpc的服务经过某种方式获取路由表配置,从获取的路由表中寻找映射的appid,和被调用目标appid(调用方必定知道被掉方的appid)进行对比,找到须要调用的服务地址和端口,例如:
feature_x的请求Rx到达app_srv0以后,app_srv0须要调用app_srv1,app_srv0解析出请求头部(comm_head)的route_id:feature_x_20180101,使用route_id获取到路由表feature_x_20180101项下面的全部配置,从得到的配置中寻找appid=app_srv1的项(这里服务的数量是颇有限的,没必要担忧遍历的性能),获取对应的ip和端口发起调用,这样就能够是Rx请求触发的第一次rpc调用的是feature_x版本的app_srv1_x而不是基线版本的app_srv1或者feature_y版本的app_srv1_y,若是找不到对应的想就采用默认路由调用,也就是调用基线版本;发起调用的时候透传请求头里面的route_id,这样后续调用链的每一个服务都能使用上述过程找到请求要调用的版本分支,这里须要增长的就是对路由表的维护工做以及客户端发起请求的时候须要增长请求头的写入。发布上线以后也能够采用这种方法进行AB test,若是不须要能够对配置进行修改或者前端发起请求的时候不携带路由信息。
1)交给mesh
2)服务发布的时候经过注册中心注册本身版本的路由信息,经过注册中心下发到各服务所在的机器,服务框架经过agent从本地读取配置
本身的一点思考,欢迎更多交流!!!