在一个调用链非常长的功能中,如果想修改其中的一个特性,并进行测试,而又不影响该环境的其他用户使用现有功能、特性,例如:
1. A、B、C、D之间通过Dubbo实现远程调用
2. 这些模块可能有一个或者多个实例
3. 此环境由多个人员(包括开发、测试)同时使用
此时若想修改B中的某个功能,增加一个特性(称为FAT1),并且也注册到此环境中,则会发生如下问题:
当其他的用户从使用此功能时,从A发起的调用可能会由于Dubbo带的负载均衡算法等原因,在带有FAT1和不带有FAT1的实例间来回切换,最后的表现可能就是某一个功能使用两次,产生的结果竟然不一样!
解决这个问题最简单的方法就是给每个功能特性(FAT)独立设置一个测试环境,例如这一期有20个功能特性上线,就部署20个环境好了。。。。等等,是不是哪里不对?部署20个环境?你是否感觉到你BOSS站在你座位后面,随时准备把你扔出办公室?
仔细分析这个问题,要解决的重点有两个:
1. 将不同人员进行开发/测试的特性隔离开
2. 不修改的部分尽量共享,以节省资源
综上,最好的解决方案应该是如下图所示:
1. 建立一个Baseline环境,该环境包含了应用程序所需的所有组件、数据集等
2. 对于不同的功能特性,为该特性修改的组件独立发布一个实例,称之为一个Feature,对应的测试场称之为FAT+编号,例如Feature 1的测试环境称为FAT1
3. 开发和测试某个功能特性(例如Feature 1)时,利用路由功能让上游模块自动选择正确的下游模块,便于开发人员调试以及测试人员查看效果
通过对Dubbo文档的探索(http://dubbo.apache.org/zh-cn/docs/user/demos/routing-rule.html),发现实现此功能的方案有如下几种:
1. 使用条件路由规则
2. 使用动态标签功能
3. 使用静态标签功能
经过对上述三种方法的分析,发现各自的优缺点如下:
1. 如果使用条件路由:
优点是需求明晰,如果我想设计一个FAT测试场,其中A、B是待测试组件,可以使用路由规则host != A => host !=B和host = A => host = B
缺点是:
A. 需要使用Dubbo控制台修改路由规则,对于一般的开发/测试来说,权限太大了
B. 如果组件A、B、D同时修改了,当请求从A->B->C传递时,C不一定知道这个请求是否应该传到D,使用条件路由无法实现
2. 如果使用动态标签,1中的问题B能够得到解决,因为标签在整个调用链路中都会以Attachment的形式被传递,但是A问题依然无法解决
综上,要实现此功能,最好是使用3. 静态标签功能,根据官方文档,Dubbo的标签路由功能是2.7.0开始才可用的(坑巨多,下面会一一说明),所以我们需要使用这个版本。
为了简化(偷)步骤(懒),我们把问题变为A->B->C这种三模块调用过程,本质上设计的调用路由问题还是一样的。
先建立三个spring boot工程:组件svcA、svcB和svcC
两个模块间调用使用的facade工程,以及他们所共享的父工程,总共六个工程如下图:
他们之间的关系如下:
其中callfromsvcA2svcB是A调用B使用的facade,而callfromsvcB2svcC是从B调用C时的facade,取名方式略暴力,品位低,敬请理解
下面进入踩坑之旅:
1. 导入Dubbo 2.7.0
因为Dubbo 2.7.0才支持tag路由功能,所以我们必须先导入它到工程,但是当你实践时,你会发现。。。。。。网上的教程(包括官方文档):都!是!骗!人!的!
官方的说明是:http://dubbo.apache.org/zh-cn/docs/user/versions/version-270.html
<properties> <dubbo.version>2.7.0</dubbo.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-dependencies-bom</artifactId> <version>${dubbo.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>${dubbo.version}</version> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> </dependency> </dependencies>