一、需求
云服务产品,微服务架构,使用的Dubbo 框架。由于种种原因目前使用的版本是 Dubbox2.8.4 。业务开发人员的痛点:他们每个人的开发范围一般固定在某一个微服务内,但是如果想完成某个接口的测试、调试工作、联调工作,本机需要启动很多服务,效率低下,苦不堪言。
这个问题的现状是这样的:
1、dubbo里自带的group、version服务分组隔离方案是不能解决我现在面临的问题的;
2、有网友是从自定义ip黑白名单的角度来实现 路由的,同样也解决不了我现在面临的问题;
3、apache dubbo 2.7.X版本里面新增了 TagRoute 路由策略,但是我的dubbo版本里没有此路由特性,各种原因,不能立刻升级到新版本;
于是干脆按照TagRoute的思路手撕一个MyTagRouter路由策略吧
二、方案
利用dubbo的RouterFactory和Router接口进行SPI扩展,通过识别请求中的mytag变量来准确路由到正确的服务上,如果没有找到,则进行降级处理,路由到不带有mytag 的服务上
三、实现
1、Router接口的实现
public class MyRouter implements Router {
private static final Logger logger = LoggerFactory.getLogger(MyRouter.class);
private final URL url;
public MyRouter(URL url){
this.url = url;
}
@Override
public URL getUrl() {
return this.url;
}
@Override
public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
if (invokers != null && invokers.size() != 0) {
try {
//获取需要自定义路由的tag
String mytag = TraceUtil.getMytag();
logger.info("mytag:"+mytag);
List<Invoker<T>> result = new ArrayList();
while (true){
Iterator var5 = invokers.iterator();
while(var5.hasNext()) {
Invoker<T> invoker = (Invoker)var5.next();
//方案1:修改下源码 RegistryDirectory.InvokerDelegete 内部类由private改为public即可
//URL providerUrl = ((RegistryDirectory.InvokerDelegete) invoker).getProviderUrl();
//方案2:杀手锏,反射获取
URL providerUrl = null;
try {
Class<?>[] declaredClasses = RegistryDirectory.class.getDeclaredClasses();
for (Class<?> declaredClass : declaredClasses) {
if (declaredClass.getName().contains("InvokerDelegete")) {
Field providerUrlField = declaredClass.getDeclaredField("providerUrl");
providerUrlField.setAccessible(true);
providerUrl = (URL) providerUrlField.get(invoker);
logger.info("providerUrl:"+providerUrl.toString());
break;
}
}
}catch (Exception e){
logger.error(e.getMessage());
providerUrl = null;
}
//提供者服务名称
String applicationVersion = providerUrl.getParameter("application.version");
if(mytag==null){
//排除所有debug提供者
if (applicationVersion==null) {
result.add(invoker);
}
}else{
//添加所有符合mytag的提供者
if (applicationVersion!=null&&applicationVersion.equals(mytag)) {
result.add(invoker);
}
}
}
if (result.size()==0&&mytag!=null) {
//不存在mytag对应的debug服务,下面要走正常服务了
mytag=null;
}else{
//存在mytag对应的debug服务
break;
}
}
return result;
} catch (Throwable var7) {
logger.error("Failed to execute tag router rule: " + this.getUrl() + ", invokers: " + invokers + ", cause: " + var7.getMessage(), var7);
}
}
return invokers;
}
@Override
public int compareTo(Router o) {
return 0;
}
}
2、MyRouterFactory接口的实现
public class MyRouterFactory implements RouterFactory {
@Override
public Router getRouter(URL url) {
return new MyRouter(url);
}
}
3、对dubbo服务进行mytag标签的设置,mytag默认是空的,在开发者机器上设置下这个环境变量,启动服务就会自动生成一个带mytag标签的服务,这样才能根据mytag准确路由过来
<dubbo:application name="xxx-service" version="${mytag:}"/>
4、META-INF/dubbo/com.alibaba.dubbo.rpc.cluster.RouterFactory的配置
myRouterFactory=com.xxx.dubbo.router.MyRouterFactory
5、自定义路由策略生效设置
<dubbo:registry address="xxx" group="xxx" >
<dubbo:parameter key="router" value="myRouterFactory" />
</dubbo:registry>
6、设置mytag的工具类(TraceUtil是一个使用ThreadLocal实现的工具类,用来进行mytag的传递)
public class MytagUtil {
public static void setMytag(){
String mytag = System.getenv("mytag");
RpcContext.getContext().setAttachment("mytag",mytag);
TraceUtil.setMytag(mytag);
}
}
7、mytag的传递,依赖dubbo的自定义过滤器(一个消费者过滤器,一个提供者过滤器),持续传递下去
消费者过滤器ConsumingFilter
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
String mytag = TraceUtil.getMytag();
RpcContext.getContext().setAttachment(DubboHelper.mytag, mytag);
return invoker.invoke(invocation);
}
提供者过滤器ProvidingFilter
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
String mytag = RpcContext.getContext().getAttachment(DubboHelper.mytag)
TraceUtil.setMytag(mytag);
return invoker.invoke(invocation);
}
四、总结
1、路由的逻辑
1)如果当前线程中含有的mytag!=null,则根据mytag的内容过滤含有mytag的invokerList,得到带有mytag的invoker;
2)如果过滤后没有符合条件的invoker则进行降级处理,选用不带有mytag的invoker;
3)如果还是没有获取到invoker就直接报错了;
4)无论如何是不可能路由到其它开发者的机器上。
2、提供者是通过providerUrl中的application.version参数来暴露mytag值给消费者端的,但是实际上在获取这个参数过程中遇到了一些问题。在route方法的List<Invoker<T>> invokers 参数中只能获取到合并后的url地址,而合并后的url中application.version参数消失了。
为什么会消失呢?我还要接着去研究(应该是和参数的合并优先级有关)。为了能尽快使用起来解决研发的问题,如代码所示,目前我通过一些非常规手段获取到了invoker中的providerUrl的值,也达到了目的,但是不太优雅,架构要讲究优雅的。
3、下一步要研究源代码测试清楚 为什么 application.version不能传递过来,按照dubbo的优先级策略,我在provider端设置了值,在consumer端没有设置值,这时候provider端的参数是会生效的,合并后应该出现在url中。
4、目前已经在项目中开始应用,非常好使^_^