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 或解析
AppRouter 150 dubbo-consumer
ServiceRouter 140 com.anla.rpc.service.HelloService
TagRouter 100 目前是從系統當前讀取,即application.tag-router,而不具有直接監聽在線配置變更
MockInvokersSelector Integer.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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章