考虑到研发在项目开发中,因微服务化带来的依赖服务不稳定、联调不方便的问题,为减少开发过程中,因环境问题消耗的成本,现基于dubbo的源码的扩展,提供项目环境组网功能。
项目组网效果
部署说明
项目环境由本地机器启动的应用与开发环境的应用组成。
如上图案例,当前开发的项目涉及变更应用有
- 用户A负责的service-A,service-B在本地机器启动
- 用户B负责的service-D,在本地机器启动。
- 用户B同时在本地启动XXX-web,作为项目环境组网的入口。
调用链路
示例中,APP/H5向后端发起一个请求,调用顺序为
APP/H5 => XXX-web => service-A => service-B => service-C => service-D
- 请求到达用户B机器的XXX-web
- XXX-web需要请求service-A服务,此服务同时存在用户A机器与开发环境,因为用户机器的的服务优先级高,所以请求到用户A机器service-A.
- service-A.需要请求service-B服务,此服务同时存在用户A机器与开发环境,因为用户机器的的服务优先级高,所以请求到用户A机器service-B.
- service-B.需要请求service-C服务,此服务同时仅存在开发环境,所以请求到开发环境的service-C
- service-C需要请求service-D服务,此服务同时存在用户B机器与开发环境,因为用户机器的的服务优先级高,所以请求到用户B机器service-D.
服务调用过程
服务调用时,可以简单分为两个阶段,
- 获取服务提供者列表
- 远程调用服务
下面分别分析。
获取服务提供者列表
远程调用服务
获取服务提供者列表时,既能获取到项目环境的服务提供者,也能获取到稳定环境的服务提供者。
远程调用服务时,
- 如果服务存在项目环境,则优先调用项目环境的服务,如项目环境服务未找到,则调用稳定环境的服务
- 服务请求从项目环境的服务A调到稳定服务B,当服务B需要调用服务的下一个服务C,
- 如果服务C在项目环境存在,则调用到项目环境的服务C
- 否则,调用到开发环境的服务C
分组特性
消费者的group设置为*时,有以下特点
- 在查找服务提供者时,只需要匹配服务接口。
- 在调用服务时,可以匹配所有group不为空的服务提供者。
dubbo分组特性,正好与我们的项目环境组网相同。
在要组网时,所有相关开发约定一个dubbo group(假定为dev1)。需要在本地启动的应用使用"dev1"作为服务group。
< dubbo:reference id="xxxService" check="false" timeout = "60000" version="1.0.0" interface="com.ryze.service.opt.api.service.XXXService" group="*" />
开发环境启动的服务,统一使用group为"*"
< dubbo:reference id="xxxService" check="false" timeout = "60000" version="1.0.0" interface="com. ryze.service.opt.api.service.XXXService" group="*" />
要实前面所述的“XXX-web需要请求service-A服务,此服务同时存在用户A机器与开发环境,因为用户机器的的服务优先级高,所以请求到用户A机器service-A.”。还需要一种机制,在调用服务时,对服务列表进行过滤。
当月group为"*"是,消费调用会进入到“org.apache.dubbo.rpc.cluster.support.MergeableCluster”
而MergeableCluster的doJoin方法返回”org.apache.dubbo.rpc.cluster.support.MergeableClusterInvoker"实例。
MergeableClusterInvoker的doInvoke(Invocation,List<Invoker<T>>,LoadBalance)方法的逻辑是,将集群中的调用结果聚合起来返回结果。
这个功能有点鸡肋,我们一般用不到。但我们可以利用这里的调用链做我们服务提供者列表过滤功能。因为MergeableCluster方法是代码写死引用MergeableClusterInvoker的。
我们可以复用dubbo spi机制重写“mergeable”指向自定义的ProjectMergeableCluster,在ProjectMergeableCluster中,我们再直接引用片定义的“ProjectMergeableClusterInvoker”
package org.apache.dubbo.demo.consumer.comp;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.Directory;
import org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker;
import org.apache.dubbo.rpc.cluster.support.wrapper.AbstractCluster;
public class ProjectMergeableCluster extends AbstractCluster {
public static final String NAME = "mergeable";
@Override
public <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException {
return new ProjectMergeableClusterInvoker<T>(directory);
}
}
package org.apache.dubbo.demo.consumer.comp;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.*;
import org.apache.dubbo.rpc.cluster.Directory;
import org.apache.dubbo.rpc.cluster.LoadBalance;
import org.apache.dubbo.rpc.cluster.support.MergeableClusterInvoker;
import java.util.List;
public class ProjectMergeableClusterInvoker<T> extends MergeableClusterInvoker<T> {
public ProjectMergeableClusterInvoker(Directory directory) {
super(directory);
}
@Override
protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> availableInvokers = null;
String projectGroup = RpcContext.getContext().getAttachment("projectGroup");
if(StringUtils.isBlank(projectGroup)){
projectGroup = ProjectEnvUtils.getGroup();
}
if(!StringUtils.isBlank(projectGroup)){
for(Invoker<T> invoker: invokers){
if(projectGroup.equals(invoker.getUrl().getParameter(CommonConstants.GROUP_KEY))){
availableInvokers.add(invoker);
}
}
}
if(CollectionUtils.isEmpty(availableInvokers)){
availableInvokers = invokers;
}
return super.doInvoke(invocation, availableInvokers, loadbalance);
}
}
ProjectMergeableClusterInvoker继承了MergeableClusterInvoker,在ProjectMergeableClusterInvoker的doInvoke方法中,先完成服务列表invokers的过滤,然后再调用父类MergeableClusterInvoker的doInvoke。