前面花了挺多時間,陸陸續續研讀了Dubbo 消費端和服務端的啓動流程,配置讀取等,後面又研究了 Dubbo 代理對象生成,以及調用過程。
這篇文章開始將開始將研究的Dubbo 集羣 路由功能,主要包括以下幾個節點探究:
- 初始化
- 調用時間
- 種類及源碼
- 更新操作
本篇例子是以單消費者,多服務提供者來進行,源碼位於:
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 中一些信息,所以只有 AppRouterFactory
、MockRouterFacotry
、TagRouterFactory
、ServiceRouterFactory
。
那麼以 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 過程有以下幾步:
- 將自己註冊到configuration,當註冊中心變更時得到通知
- 第一次嘗試讀取routerKey,如果有數據嘗試解析
監聽器接口爲 ConfigurationListener
,當有變化則會執行 子類實現的 process
。
調用時間
由於上文中,配置的是 app 維度的 ,名字爲 dubbo-provider
的 路由協議,但是 這個協議同樣屬於 com.anla.rpc.service.HelloService
。所以當初始化ServiceRouter
時候,仍然會讀取到這兩條condition
。
這樣一來,我們就在com.anla.rpc.service.HelloService
的 ServiceRouter
中會有兩條路由協議。
在 RegistryDirectory
調用 list
時候,會使用到 路由。
RegistryDirectory
中維護了一些和服務提供者有關的信息,通過服務目錄,服務消費者可獲取到服務提供者的信息,當然也能從註冊中心獲取可用的 invoker
,而後在調用 list
方法時,會使用 Router
進行進一步過濾 。
而後通過 RegistryDirectory
中doList
-> RouterChain
的 router
:
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;
}
而後會調用每一個Router
的route
方法進行invokers
的過濾。
更新流程
當從註冊中心更新時,會以怎樣一種方式更新Router呢?
由於往註冊中心註冊了 節點更新事件,本文以ZooKeeper
爲例,所以當在 DubboAdmin
更新了 Router 信息後,會調用 ConfigurationListener
的 process
。而對於 Router
來說, 只有 ListenableRouter
和 TagRouter
有實現 ConfigurationListener
,所以進一步篩選後,則會調用到他們的 process
方法。
而前面分析可知,Router
中process
方法就是對 Router
配置信息進行解析,而後存入其中的conditions
中,而後當 有 RegistryDirectory
調用 list ,而後會調用 Router
的 route
方法,對已有的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