Dubbo进阶(十三)- Dubbo中路由使用及源码分析

前面花了挺多时间,陆陆续续研读了Dubbo 消费端和服务端的启动流程,配置读取等,后面又研究了 Dubbo 代理对象生成,以及调用过程。
这篇文章开始将开始将研究的Dubbo 集群 路由功能,主要包括以下几个节点探究:

  1. 初始化
  2. 调用时间
  3. 种类及源码
  4. 更新操作

本篇例子是以单消费者,多服务提供者来进行,源码位于:
router
启动provider时,通过指定不同端口,从而产生多个实例。
而通过在 dubbo admin上配置 路由协议如下:

enabled: true
force: true
runtime: true
conditions:
  - 'interface=HelloService&method=hello=>address=*:20880'
  - 'interface=HelloService&method=hello2=>address=*:20882'

最终结果 hello 方法则只会在 20880 端口对应服务调用,而 hello2 则只会在20882 服务端口调用。

简单的例子程序之后,开启Router之旅!!

初始化

Dubbo 消费者中 代理对象 初始化详解 时候,提到了 RegistryProtocol 注册中心和集群的初始化,其中在 它的 doRefer 就是进行了 Router 的初始化:directory.buildRouterChain(subscribeUrl);
RegistryDirectory:存储了一些和服务提供者有关的信息,通过服务目录,服务消费者可获取到服务提供者的信息,比如 ip、端口、服务协议等。通过这些信息,服务消费者就可通过 Netty 等客户端进行远程调用

buildRouterChain

先看涉及到的代码:

    public void buildRouterChain(URL url) {
    // 调用 setRouterChain
        this.setRouterChain(RouterChain.buildChain(url));
    }

RouterChain.buildChain(url):

    public static <T> RouterChain<T> buildChain(URL url) {
    // 新建一个 RouterChain
        return new RouterChain<>(url);
    }

RouterChain 主要 维护invokers 集合,以及 routers集合:

    // 维护从 注册中心获取的 
    private List<Invoker<T>> invokers = Collections.emptyList();

    // 维护所有的路由规则,当有 改变时,由 ‘route://’ 改变.
    private volatile List<Router> routers = Collections.emptyList();

    // 内置 的 路由实例,即 各种路由实例集合,包括ConfigConditionRouter、TagRouter等
    // instance will never delete or recreate.
    private List<Router> builtinRouters = Collections.emptyList();

RouterChain 构造方法:

    private RouterChain(URL url) {
    // 加载 spi 类
        List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class)
                .getActivateExtension(url, (String[]) null);
        // 根据  RouterFactory 获取具体的Router
        List<Router> routers = extensionFactories.stream()
                .map(factory -> factory.getRouter(url))
                .collect(Collectors.toList());
		// 初始化Router
        initWithRouters(routers);
    }

RouterFactory 的 SPI 文件:

file=org.apache.dubbo.rpc.cluster.router.file.FileRouterFactory
script=org.apache.dubbo.rpc.cluster.router.script.ScriptRouterFactory
condition=org.apache.dubbo.rpc.cluster.router.condition.ConditionRouterFactory
service=org.apache.dubbo.rpc.cluster.router.condition.config.ServiceRouterFactory   # order=300
app=org.apache.dubbo.rpc.cluster.router.condition.config.AppRouterFactory               # order = 200
tag=org.apache.dubbo.rpc.cluster.router.tag.TagRouterFactory                                     # order = 100
mock=org.apache.dubbo.rpc.cluster.router.mock.MockRouterFactory                            # order = 0

上面首先通过加载SPI 文件,并且将@Adaptive 类都加载出来,所以最终加载出来为以下四个:

ServiceRouterFactory
AppRouterFactory
TagRouterFactory
MockRouterFactory

而后则会调用 这些工厂方法的 getRouter 方法。

Router 种类

这一小节有必要将Router类结构贴出来,在Dubbo中,基本都是使用工厂模式,使用 RouterFactory 产生 Router

在这里插入图片描述
所以Router 分为两类: ListenableRouter 和 非 ListenableRouter 类型。 ListenableRouter 即 可以 动态监听 注册中心变化,从而 是可以动态变化的。

RouterFacotry 类图

以下图展示了 RouterFacotry 中一些信息,所以只有 AppRouterFactoryMockRouterFacotryTagRouterFactoryServiceRouterFactory
在这里插入图片描述

那么以 AppRouter 为例看下实现:

AppRouter

​AppRouter 由 AppRouterFactory 的 getRouter 方法调用,而从 RouterChain 的私有构造方法中如下调用,

        List<Router> routers = extensionFactories.stream()
                .map(factory -> factory.getRouter(url))
                .collect(Collectors.toList());

AppRouterFactory 中 getRouter

    @Override
    public Router getRouter(URL url) {
    // 判断router
        if (router != null) {
            return router;
        }
        // 加锁 创建
        synchronized (this) {
            if (router == null) {
                // 具体执行createRouter
                router = createRouter(url);
            }
        }
        return router;
    }

createRouter方法:

    private Router createRouter(URL url) {
        return new AppRouter(DynamicConfiguration.getDynamicConfiguration(), url);
    }

接下来在 AppRouter 中构造方法:

    public AppRouter(DynamicConfiguration configuration, URL url) {
        super(configuration, url, url.getParameter(CommonConstants.APPLICATION_KEY));
        this.priority = APP_ROUTER_DEFAULT_PRIORITY;
    }

在ListenableRouter 中构造方法:

    public ListenableRouter(DynamicConfiguration configuration, URL url, String ruleKey) {
    // 填充configuration 和 url
        super(configuration, url);
        // 默认force 为 false
        this.force = false;
        // 
        this.init(ruleKey);
    }

上面代码主要是初始化相关工作,根据不同的RouterFactory,执行不同的getRouter。最终在init 中执行具体初始化逻辑:

    private synchronized void init(String ruleKey) {
        if (StringUtils.isEmpty(ruleKey)) {
            return;
        }
        String routerKey = ruleKey + RULE_SUFFIX;
        // 将该Router 注册到configuration中
        configuration.addListener(routerKey, this);
        // 尝试第一次取,看有没有 已配置的 router
        String rule = configuration.getConfig(routerKey);
        if (StringUtils.isNotEmpty(rule)) {
        // 有就解析
            this.process(new ConfigChangeEvent(routerKey, rule));
        }
    }

上面init 过程有以下几步:

  1. 将自己注册到configuration,当注册中心变更时得到通知
  2. 第一次尝试读取routerKey,如果有数据尝试解析

监听器接口为 ConfigurationListener,当有变化则会执行 子类实现的 process

调用时间

由于上文中,配置的是 app 维度的 ,名字为 dubbo-provider 的 路由协议,但是 这个协议同样属于 com.anla.rpc.service.HelloService。所以当初始化ServiceRouter时候,仍然会读取到这两条condition
在这里插入图片描述
这样一来,我们就在com.anla.rpc.service.HelloServiceServiceRouter中会有两条路由协议。

RegistryDirectory 调用 list 时候,会使用到 路由。

RegistryDirectory 中维护了一些和服务提供者有关的信息,通过服务目录,服务消费者可获取到服务提供者的信息,当然也能从注册中心获取可用的 invoker,而后在调用 list 方法时,会使用 Router 进行进一步过滤 。

而后通过 RegistryDirectorydoList -> RouterChainrouter
RouterChain 的router:

    public List<Invoker<T>> route(URL url, Invocation invocation) {
        List<Invoker<T>> finalInvokers = invokers;
        // 对每一个 routers 进行遍历,执行其route方法进行过滤
        for (Router router : routers) {
            finalInvokers = router.route(finalInvokers, url, invocation);
        }
        return finalInvokers;
    }

而后会调用每一个Routerroute 方法进行invokers 的过滤。

更新流程

当从注册中心更新时,会以怎样一种方式更新Router呢?
由于往注册中心注册了 节点更新事件,本文以ZooKeeper 为例,所以当在 DubboAdmin 更新了 Router 信息后,会调用 ConfigurationListenerprocess。而对于 Router 来说, 只有 ListenableRouterTagRouter 有实现 ConfigurationListener,所以进一步筛选后,则会调用到他们的 process 方法。

而前面分析可知,Routerprocess 方法就是对 Router 配置信息进行解析,而后存入其中的conditions 中,而后当 有 RegistryDirectory 调用 list ,而后会调用 Routerroute 方法,对已有的Invokers 进行进一步 筛选

权重

以下介绍了 几个Router 的 权重以及介绍,侧面了反映了Router 优先级问题。

权重注册中心解析的key 或解析
AppRouter150dubbo-consumer
ServiceRouter140com.anla.rpc.service.HelloService
TagRouter100目前是从系统当前读取,即application.tag-router,而不具有直接监听在线配置变更
MockInvokersSelectorInteger.MIN_VALUE主要用来实现mock,当接口是mock特性,route方法将会排除所有非mock的invoker,只返回mock的
ConditionRouter默认为0,从url中读取主要用于存储 ConditionRunleParse 解析后的规则
ScriptRouter从url中读,默认为0

默认从小到大排序

后语

本文以一个例子开头,深入源码挖掘了 Dubbo 中 Router 实现,从 初始化,调用,更新三个方面介绍了Router 原理,但是本文没有介绍具体配置 Router 的规则以及 Router 规则解析的细节,
当然这些可以不妨碍从宏观去了解Router,有兴趣可以去Dubbo 官方文档中获取相关知识。

相关 配置规则以及解析源码可以看 https://dubbo.apache.org/zh-cn/docs/user/demos/routing-rule.html
https://dubbo.apache.org/zh-cn/docs/source_code_guide/router.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值