Dubbo 框架的分布式开发调试方案

一、需求

云服务产品,微服务架构,使用的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 的服务上

aced0b4f2072d95f08c0c6b5be388d38.png

三、实现

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、目前已经在项目中开始应用,非常好使^_^

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吕哥架构

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值