一、需求
云服务产品,微服务架构,使用的Dubbo 框架。由于种种原因目前使用的版本是 Dubbox2.8.4 。业务开发人员的痛点:他们每个人的开发范围一般固定在某一个微服务内,但是如果想完成某个接口的测试、调试工作、联调工作,本机需要启动很多服务,效率低下,苦不堪言。
这个问题的现状是这样的:
1、dubbo里自带的group、version服务分组隔离方案是不能解决我现在面临的问题的;
2、有网友是从自定义ip黑白名单的角度来实现 路由的,同样也解决不了我现在面临的问题;
3、apache dubbo 2.7.X版本里面新增了 TagRoute 路由策略,但是我的dubbo版本里没有此路由特性,各种原因,不能立刻升级到新版本;
于是干脆手撕一个MyTagRouter路由策略吧
二、方案
利用Dubbo的SPI机制进行Router扩展,实现个性化路由。
1、provider端增加自定义参数mytag,暴露到url中,导出到注册中心,从而传递到订阅端的consumer;
2、consumer端通过自定义路由策略进行提供者集合过滤;
3、如果当前请求线程中携带了mytag变量,则寻找与存在mytag并且内容一致的provider;
4、如果当前请求线程中没有携带mytag变量,则寻找不存在mytag的provider;
5、如果当前请求携带了mytag,但是没有找到满足条件的provider,则降级到寻找不带有mytag的provider上;
6、无论如何都不会使用mytag内容不一致的provider;
7、最终可以实现自由穿梭,到达正确的provider;

三、实现
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;}@Overridepublic URL getUrl() {return this.url;}@Overridepublic <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {if (invokers != null && invokers.size() != 0) {try {//获取需要自定义路由的tagString mytag = TraceUtil.getMytag();logger.info("mytag:"+mytag);List<Invoker<T>> result = new ArrayList();while (true){Iterator iterator = invokers.iterator();while(iterator.hasNext()) {Invoker<T> invoker = (Invoker)iterator.next();URL invokerUrl = invoker.getUrl();//获取提供者携带的mytag参数String providerMytag = invokerUrl.getParameter("mytag");if(mytag==null){//排除所有debug提供者if (providerMytag==null) {result.add(invoker);}}else{//添加所有符合mytag内容一致的提供者if (providerMytag!=null&&providerMytag.equals(mytag)) {result.add(invoker);}}}if (result.size()==0&&mytag!=null) {//不存在mytag内容一致的debug服务,下面要走正常服务了mytag=null;}else{//存在mytag对应的debug服务break;}}return result;} catch (Throwable throwable) {logger.error("Failed to execute tag router rule: " + this.getUrl() + ", invokers: " + invokers + ", cause: " + throwable.getMessage(), throwable);}}return invokers;}@Overridepublic int compareTo(Router o) {return 0;}}
2、MyRouterFactory接口的实现
public class MyRouterFactory implements RouterFactory {@Overridepublic Router getRouter(URL url) {return new MyRouter(url);}}
3、对dubbo服务进行mytag标签的设置,mytag默认是空的,在开发者机器上设置下这个环境变量,启动服务就会自动生成一个带mytag标签的服务,这样才能根据mytag准确路由过来
<dubbo:provider><dubbo:parameter key="mytag" value="${mytag:}"/></dubbo:provider>
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
@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {String mytag = TraceUtil.getMytag();RpcContext.getContext().setAttachment(DubboHelper.mytag, mytag);return invoker.invoke(invocation);}
提供者过滤器ProvidingFilter
@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {String mytag = RpcContext.getContext().getAttachment(DubboHelper.mytag)TraceUtil.setMytag(mytag);return invoker.invoke(invocation);}
四、总结
1、关键点梳理,新增自定义的mytag参数必须设置到<dubbo:provider>配置中,不能设置到 <dubbo:application>中,更不能设置到<dubbo:consumer>中,原因在于dubbo参数的优先级覆盖上(我开发的第1版中就是因为将参数设置到了dubbo:application中从而导致问题,最后使用不优雅的代码解决问题,虽然也实现了想要的功能,但是代码的实现不够优雅,根本原因在于参数的位置放错了)
2、此方案简单易用,目前已经在项目中开始广泛应用,非常好使^_^
3、使用步骤:
1)开发人员设置一个本机环境变量即可
2)注册中心统一使用dev服务器的注册中心
3)本地启动想要调试、测试的服务
4)在发起请求的地方带上mytag环境变量(如果从浏览器/postman进入网关进行测试,则需要在header或者url中增加mytag变量;如果直接绕过网关进行RPC方法调用,则在dubbo的消费者端使用MytagUtil工具进行mytag环境变量设置)




