dubbo解析-一文让你明白Route路由的来龙去脉

本文基于dubbo 2.7.5版本代码


Route指的是路由。

一、Route路由的作用

在dubbo中,Route是接口,每个实现类代表了一个路由规则,当客户端访问服务端时,dubbo根据路由规则筛选出合适的服务提供者列表,之后通过负载均衡算法再次筛选。下面代码展示了FailbackClusterInvoker中的invoke方法的一部分(FailbackClusterInvoker的invoke方法继承自AbstractClusterInvoker):

		List<Invoker<T>> invokers = list(invocation);//list方法中遍历了每个路由规则,筛选出服务提供者列表
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);//初始化负载均衡策略
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        return doInvoke(invocation, invokers, loadbalance);//在doInvoke方法中使用负载均衡筛选出最终的服务提供者,并调用

二、如何置Route规则

dubbo里面指定路由规则的设置比较复杂,没有对应的属性可以设置的。
默认加载四个Route实现类:

  1. ServiceRouter
  2. TagRouter
  3. MockInvokersSelector
  4. AppRouter

这些实现类是由对应的RouterFactory创建的,设置的路由规则其实是加载指定的RouterFactory实现类。RouterFactory再创建对应的Router对象。
RouterFactory实现类有注解@Activate,因此RouterFactory实现类是通过调用getActivateExtension完成加载。默认情况下,凡是有@Activate的类,dubbo都会加载。代码如下:

ExtensionLoader.getExtensionLoader(RouterFactory.class).getActivateExtension(url, "router");

因此在客户端我们可以利用@Activate注解指定路由规则:
@Activate注解上一般设置有属性值,比如@Activate(order = 100),如果需要设置不加载哪个路由规则,则在@Reference的parameters中增加order属性设置,设置order的值与@Activate的不一致即可。但是这个方法有很大的局限性,仅仅能设置不加载哪个,而且对于没有@Activate注解属性值的路由规则无法剔除,另外order属性是用于排序的。
服务端也可以设置路由规则,在服务端的@Service里面指定parameters的值,如下:

@Service(parameters = {"router", "tag","category","routers"})

客户端可以从注册中心获取到该route的参数设置。但是这个方法只能增加客户端的路由规则,无法去除之前的路由规则。

三、dubbo如何使用路由规则

dubbo使用RouterChain找到所有的RouterFactory对象,然后由RouterFactory创建出Router对象,Router对象的集合作为RouterChain的属性,之后访问路由规则都是通过RouterChain完成的。代码如下:

	//调用该方法构建RouterChain对象。
	public static <T> RouterChain<T> buildChain(URL url) {
        return new RouterChain<>(url);
    }
    private RouterChain(URL url) {
    	//使用SPI找到所有的RouterFactory
        List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class).getActivateExtension(url, "router");
        //使用RouterFactory创建出Router对象
        List<Router> routers = extensionFactories.stream().map(factory -> factory.getRouter(url)).collect(Collectors.toList());
        initWithRouters(routers);
    }
    public void initWithRouters(List<Router> builtinRouters) {
        this.builtinRouters = builtinRouters;
        this.routers = new ArrayList<>(builtinRouters);
        this.sort();//每个Route对象都实现了Comparable接口,排序按照字段priority的值升序排列
    }

每次客户端启动的时候,都会使用buildChain方法构建RouterChain对象。RouterChain使用routers保存所有的Route对象集合,这里注意一点,builtinRouters在客户端启动完毕后不会再发生改变,它记录了客户端设置的Route对象集合;而routers属性是会发生改变的,每当新增一个远程服务提供者时,客户端会收到注册中心的通知,之后解析服务端的参数,如果服务端配置了路有规则,那么routers属性值便是服务端设置的路有规则对象与builtinRouters的并集。
下图展示了客户端在何时调用buildChain方法:
在这里插入图片描述
RegistryDirectory对象是服务目录,记录了所有的远程服务提供者。客户端启动每次需要创建远程代理对象(调用ReferenceConfig的createProxy方法)的时候,都会创建RegistryDirectory对象,构建RouterChain是RegistryDirectory对象初始的一部分,RegistryDirectory的属性routerChain记录了RouterChain对象。
构建完路由规则对象后,下面介绍一下dubbo如何使用。
之前在介绍集群容错(https://blog.csdn.net/weixin_38308374/article/details/105802044)的时候,提到每次客户端访问远程服务时,都会调用AbstractClusterInvoker的invoke方法,在该方法里面调用list方法,list方法再调用RegistryDirectory.list方法,最后在该方法里面会执行下面的 代码:

		try {
            invokers = routerChain.route(getConsumerUrl(), invocation);
        } catch (Throwable t) {
            logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
        }

上面的代码中,RouterChain的route方法便是调用了路由规则筛选出合适的远程服务提供者集合。RouterChain的route方法代码如下:

	public List<Invoker<T>> route(URL url, Invocation invocation) {
        List<Invoker<T>> finalInvokers = invokers;
        //遍历Router集合
        for (Router router : routers) {
            finalInvokers = router.route(finalInvokers, url, invocation);
        }
        return finalInvokers;
    }

四、dubbo路由规则实现原理

dubbo的路由规则是由Router接口的实现类实现的。本文只介绍默认的四个实现类。

1. TagRouter

该类的priority优先级属性是100,是第二个被访问的路由规则。
该路由规则判断是否配置tag属性,tag属性值是标签的名字,与一个ip+端口集合对应,如果配置了,则找到这个ip+端口集合,并根据集合筛选出对应的远程服务提供者。

	public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
		//TagRouterRule是标签与ip+端口集合的对应关系,持有一个Tag集合,每个Tag对象
		//是一个标签与ip+端口集合的对应,该值有两种方式设置,
		//一种是监听事件ConfigChangedEvent,另一种是在客户端启动的时候检查
		//GovernanceRuleRepository是否配置了规则,GovernanceRuleRepository
		//是一种配置中心,以后介绍
        final TagRouterRule tagRouterRuleCopy = tagRouterRule;
        if (tagRouterRuleCopy == null || !tagRouterRuleCopy.isValid() || !tagRouterRuleCopy.isEnabled()) {
        	//如果没有配置TagRouterRule,则检查是否配置了tag属性,在dubbo2.7.5版本里
        	//面,tag属性值是@Reference里面指定的“dubbo.tag”值。
        	//filterUsingStaticTag方法首先检查@Reference是否配置了tag属性,
        	//如果配置了便返回服务端配置的tag值相等的服务提供者集合
        	//没有则将未配置tag属性的远程服务提供者集合返回
            return filterUsingStaticTag(invokers, url, invocation);
        }
        List<Invoker<T>> result = invokers;
        //在dubbo2.7.5版本,下面tag值从url.getParameter(TAG_KEY)来,
        //是在@Reference或者@Service中配置
        String tag = StringUtils.isEmpty(invocation.getAttachment(TAG_KEY)) ? url.getParameter(TAG_KEY) :invocation.getAttachment(TAG_KEY);
        if (StringUtils.isNotEmpty(tag)) {
        	//获取tag值对应的ip+端口集合
            List<String> addresses =tagRouterRuleCopy.getTagnameToAddresses().get(tag);
            if (CollectionUtils.isNotEmpty(addresses)) {
            	//找到与上面地址一致的远程服务提供者
                result = filterInvoker(invokers, invoker -> addressMatches(invoker.getUrl(), addresses));
                if (CollectionUtils.isNotEmpty(result) || tagRouterRuleCopy.isForce()) {
                    return result;
                }
            } else {
            	//如果配置中心没有配置tag与地址的对应关系,
            	//那么寻找与tag值一致的远程服务提供者
                result = filterInvoker(invokers, invoker -> tag.equals(invoker.getUrl().getParameter(TAG_KEY)));
            }
            if (CollectionUtils.isNotEmpty(result) || isForceUseTag(invocation)) {
                return result;
            }
            else {
            	//找到地址不一致的远程服务提供者
                List<Invoker<T>> tmp = filterInvoker(invokers, invoker -> addressNotMatches(invoker.getUrl(),
                        tagRouterRuleCopy.getAddresses()));
                //在地址不一致的远程服务提供者中找到没有配置tag属性的远程服务提供者
                return filterInvoker(tmp, invoker -> StringUtils.isEmpty(invoker.getUrl().getParameter(TAG_KEY)));
            }
        } else {
        //下面的内容没有配置tag属性的情况,即在@Reference和@Service中都没有配置tag
            List<String> addresses = tagRouterRuleCopy.getAddresses();
            if (CollectionUtils.isNotEmpty(addresses)) {
            	//找到地址不一致的远程服务提供者
                result = filterInvoker(invokers, invoker -> addressNotMatches(invoker.getUrl(), addresses));
                if (CollectionUtils.isEmpty(result)) {
                    return result;
                }
            }
            //找到配置中心不存在的tag值的远程服务提供者,并返回
            return filterInvoker(result, invoker -> {
                String localTag = invoker.getUrl().getParameter(TAG_KEY);
                return StringUtils.isEmpty(localTag) || !tagRouterRuleCopy.getTagNames().contains(localTag);
            });
        }
    }

该类的规则比较复杂。稍有不慎,就会使用错误。
上面代码提到类TagRouterRule,该类是tag标签名与服务提供者地址(IP+端口)之间的对应规则,持有一个Tag对象集合,Tag类的定义如下:

public class Tag {
    private String name;
    private List<String> addresses;
    //代码有删减
}

可以看到Tag类只有一个标签名和一个地址集合。TagRouterRule便是使用Tag集合处理tag与地址之间的对象。
TagRouterRule对象的创建是使用yaml创建的,yaml创建规则如下:

force: true
runtime: false
enabled: true
priority: 1
key: demo-provider
tags:
	- name: tag1
	addresses: [ip1, ip2]
	- name: tag2
	addresses: [ip3, ip4]

上面的配置可以在配置中心配置也可以发布事件ConfigChangedEvent,将配置设置到ConfigChangedEvent的content属性即可。

2. MockInvokersSelector

MockInvokersSelector的priority优先级是Integer.MIN_VALUE,因此该类是被访问的第一个路由规则。
该类与服务降级、Mock功能有关。
该类的route方法判断invocation.need.mock参数的的值:当需要调用服务降级时,会设置invocation.need.mock=true,默认情况下没有设置该属性。

		//默认下面的if判断是true,当服务降级时,为false
		if (invocation.getAttachments() == null) {
            return getNormalInvokers(invokers);
        } else {
        	//获取invocation.need.mock参数的值
            String value = invocation.getAttachments().get(INVOCATION_NEED_MOCK);
            if (value == null) {
                return getNormalInvokers(invokers);
            } else if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
                return getMockedInvokers(invokers);
            }
        }

MockInvokersSelector根据是否使用服务降级或者mock功能,调用getNormalInvokers或者getMockedInvokers方法。
getNormalInvokers方法检查服务提供者中是否有mock协议的提供者,如果有则返回mock协议提供者集合,如果没有就将入参invokers返回。
getMockedInvokers与getNormalInvokers相比只有一点区别,当没有mock协议的提供者时,返回null。
MockInvokersSelector总起来说是,如果需要服务降级时,筛选出mock协议提供者,如果不是服务降级,则不做任何修改将入参的服务提供者集合返回。
这里还有一点需要注意,因为dubbo没有提供mock协议,所以服务端是不能发布mock服务的,那么要是mock协议的话,只能手工修改注册中心,增加mock协议的url。可以参考下面的文章:

https://www.cnblogs.com/allenwas3/p/8427659.html

4. AppRouter、ServiceRouter

AppRouter的priority优先级属性是150。ServiceRouter的priority优先级属性是140。
这两个类的规则很类似。
两个类的路由规则其实都是委托给ConditionRouter集合。代码如下:

	public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException {
        if (CollectionUtils.isEmpty(invokers) || conditionRouters.size() == 0) {
            return invokers;
        }
        //遍历conditionRouters集合,路由规则委托为conditionRouters
        for (Router router : conditionRouters) {
            invokers = router.route(invokers, url, invocation);
        }
        return invokers;
    }

上面的代码的conditionRouters便是ConditionRouter集合。
ConditionRouter集合可以使用两种方式指定,一种是发布事件ConfigChangedEvent,该事件触发ConditionRouter集合的修改,另一种是在启动的时候通过配置中心获取配置,该配置指定了ConditionRouter集合包含了哪些ConditionRouter对象,AppRouter、ServiceRouter的区别就是访问配置中心时使用的key不一致,所以两个类的区别仅仅是在启动时,ConditionRouter集合不一致,后续如果有ConfigChangedEvent发生,那么两个类的规则便会一致。
解析配置中心的配置和ConfigChangedEvent事件中设置的配置都是使用yaml完成的。关于yaml设置规则以及ConditionRouter类可以参考文章:

http://dubbo.apache.org/zh-cn/docs/source_code_guide/router.html

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值