Dubbo 服務(wù)引入

本篇重點(diǎn)關(guān)注 Dubbo 服務(wù)引入的實(shí)現(xiàn)細(xì)節(jié)雏蛮。

服務(wù)消費(fèi)配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://code.alibabatech.com/schema/dubbo
       http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <dubbo:application name="wlm" />    
    <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
    <dubbo:protocol name="dubbo" port="20880" />

    <dubbo:reference protocol="dubbo" id="helloWorld" version="1.0"
        interface="com.wlm.dubbo.service.HelloWorld" check="false" 
        init="true" timeout="600000" />

</beans>

這里配置 reference 的 init=true束析,這樣在項(xiàng)目啟動的時(shí)候就會執(zhí)行服務(wù)引入邏輯。

服務(wù)引入入口

前面介紹服務(wù)導(dǎo)出時(shí)說過型型,Dubbo 以 Spring 方式啟動時(shí)鸿脓,標(biāo)簽屬性的解析都由 DubboBeanDefinitionParser 完成,并由 Spring 容器完成實(shí)例對象的創(chuàng)建妹懒、初始化雀监,最終得到對應(yīng) bean 的實(shí)例對象,Dubbo 服務(wù)引入對應(yīng)的 bean 為 ReferenceBean眨唬。

ReferenceBean 實(shí)現(xiàn)了 InitializingBean 接口滔悉,因此在 bean 初始化時(shí)會被調(diào)用 afterPropertiesSet() 方法。ReferenceBean 的實(shí)現(xiàn)中单绑,首先針對各類型的配置回官,判斷未配置時(shí)設(shè)置默認(rèn)值,比如:consumer搂橙、application歉提、module、monitor 等区转,最后判斷如果設(shè)置了 init=true 屬性苔巨,則直接執(zhí)行服務(wù)引入流程,否則在實(shí)際使用到時(shí)再執(zhí)行:


image.png
image.png

getObject() 是 FactoryBean 接口的方法废离,調(diào)用它可以獲取這個(gè) bean 的實(shí)例侄泽,內(nèi)部實(shí)現(xiàn)如下:


image.png
image.png

checkAndUpdateSubConfigs() 方法內(nèi)部對相關(guān)的配置進(jìn)行檢查和覆蓋;再判斷 ref 屬性是否為 null蜻韭,也就是是否執(zhí)行過服務(wù)引入流程悼尾,否則調(diào)用 init() 方法進(jìn)行服務(wù)引入柿扣。

服務(wù)引入整體的時(shí)序圖如下:


image.png
image.png

init() 方法內(nèi)部的邏輯主要分為兩部分:組裝屬性和創(chuàng)建服務(wù)代理。下面分別看看這兩部分的實(shí)現(xiàn)闺魏。

組裝屬性

組裝的屬性有:dubboVersion未状、side、interface析桥、pid司草、timestamp 等,同時(shí)還調(diào)用 appendParameters 方法將 application泡仗、consumer 等標(biāo)簽配置的屬性設(shè)置到 map 對象中:


image.png
image.png

appendParameters 方法用到第地方比較多埋虹,這里大概講一下邏輯。

調(diào)用 appendParameters 方法時(shí)娩怎,將各標(biāo)簽對應(yīng)的 config 對象傳入吨岭,比如 ApplicationConfig、ConsumerConfig峦树,appendParameters 內(nèi)部通過反射獲取類的所有 public 方法辣辫,分為兩種情況處理:

1.通過 MethodUtils.isGetter 判斷某個(gè)方法是否為 getter 類型:

public static boolean isGetter(Method method) {
    String name = method.getName();
    return (name.startsWith("get") || name.startsWith("is"))
        && !"get".equals(name) && !"is".equals(name)
        && !"getClass".equals(name) && !"getObject".equals(name)
        && Modifier.isPublic(method.getModifiers())
        && method.getParameterTypes().length == 0
        && ClassUtils.isPrimitive(method.getReturnType());
}

這里要注意的是,判斷 getter 類型條件里有一個(gè):方法返回類型必須為 “原型”魁巩,而 Dubbo 對原型的定義和 Java 中不太一樣:

public static boolean isPrimitive(Class<?> type) {
    return type.isPrimitive()
            || type == String.class
            || type == Character.class
            || type == Boolean.class
            || type == Byte.class
            || type == Short.class
            || type == Integer.class
            || type == Long.class
            || type == Float.class
            || type == Double.class
            || type == Object.class;
}

接下來就是從 config 對象中獲取屬性值急灭,并設(shè)置到 parameters 的過程,實(shí)現(xiàn)如下:


image.png
image.png

這里主要邏輯為:

  • 方法返回類型為 Object谷遂,或者getter 方法設(shè)置的 @Parameter 注解屬性 excluded=true葬馋,則跳過該方法;
  • 計(jì)算屬性 key肾扰;
  • 從 config 對象和 parameters 對象中根據(jù) key 獲取屬性值 value畴嘶,如果存在多個(gè)則拼接,并以逗號分隔集晚;
  • 最后將 key窗悯、value 設(shè)置到 parameters 對象中;

2.methodName = getParameters偷拔,且返回類型為 Map蒋院,則獲取并遍歷該 Map,將所有數(shù)據(jù)設(shè)置到 parameters 對象中:


image.png
image.png

創(chuàng)建服務(wù)代理

Dubbo 在 init() 方法中調(diào)用 createProxy 方法創(chuàng)建服務(wù)代理:


image.png
image.png

createProxy 內(nèi)部主要有四個(gè)步驟:

  1. 獲取服務(wù)地址列表莲绰;
  2. 遍歷服務(wù)地址列表欺旧,調(diào)用 Protocol.refer 方法引入服務(wù),得到遠(yuǎn)程服務(wù)的本地代理 invoker 對象蛤签;
  3. 如果得到多個(gè) invoker 對象辞友,則調(diào)用 Cluster.join 將它們合并為一個(gè) invoker 對象;
  4. 調(diào)用 ProxyFactory.getProxy 創(chuàng)建代理,將 invoker 對象轉(zhuǎn)換為 interface 對應(yīng)的 Proxy 對象称龙。

接下來就看看這四個(gè)步驟的實(shí)現(xiàn)留拾。

1. 獲取服務(wù)地址列表

Dubbo 根據(jù)是否配置了引用同一個(gè) jvm,分為兩種情況調(diào)用 refer 方法茵瀑,即本地服務(wù)和遠(yuǎn)程服務(wù)间驮。判斷方式如下:


image.png
image.png

是否引用同一個(gè) jvm 的判斷步驟如下:

  1. 配置了 injvm 屬性躬厌,則直接返回該屬性值马昨;
  2. reference 配置是否指定了 url 屬性,指定了則返回 false扛施;
  3. 判斷 scope 屬性鸿捧,為 local 則返回 true;
  4. 判斷引用的服務(wù)是否在當(dāng)前 jvm疙渣,是則返回 true匙奴;

如果調(diào)用 shouldJvmRefer 結(jié)果為 true,則服務(wù)地址為本地妄荔,并直接引入本地服務(wù):


image.png
image.png

如果調(diào)用 shouldJvmRefer 結(jié)果為 false泼菌,則引用遠(yuǎn)程服務(wù),也是我們重點(diǎn)關(guān)注的邏輯啦租。

獲取遠(yuǎn)程服務(wù)的地址邏輯如下:


image.png
image.png

這里根據(jù)是否設(shè)置了 url 屬性分成兩種情況處理哗伯。

1.設(shè)置了 url 屬性。比如:

<dubbo:reference url="127.0.0.1:20880" interface="com.wlm.dubbo.service.HelloWorld" id="helloWorld" version="1.0" />

url 內(nèi)可以拼接多個(gè)地址篷角,地址內(nèi)容即可以是引用的服務(wù)的地址焊刹,也可以是注冊中心的地址,根據(jù)協(xié)議頭區(qū)分恳蹲。

Dubbo 首先將地址字符串轉(zhuǎn)換為 URL 對象虐块,然后判斷地址類型是否為注冊中心(即協(xié)議頭是否為 registry):

  • 地址類型為注冊中心地址:將前面組裝的屬性拼接好,作為 refer 屬性添加到注冊中心 URL 對象嘉蕾;
  • 地址類型為遠(yuǎn)程服務(wù)地址:將前面組裝的屬性和 URL 對象的屬性合并贺奠。

2.未設(shè)置 url 屬性。則判斷 scope 屬性错忱,如果 scope != local敞嗡,則加載注冊中心的地址,與服務(wù)導(dǎo)出時(shí)調(diào)用的是同一個(gè)方法航背,通過入?yún)?isProvider 區(qū)分是服務(wù)提供者還是消費(fèi)者喉悴。然后將前面組裝的屬性拼接好,作為 refer 屬性添加到注冊中心 URL 對象玖媚。

注:這里還會加載監(jiān)控信息箕肃,如果有的話會作為 monitor 屬性添加到 URL 對象。這部分屬于監(jiān)控相關(guān)的邏輯今魔,
服務(wù)導(dǎo)出時(shí)也會加載勺像,不屬于本文重點(diǎn)障贸,后續(xù)單獨(dú)介紹。

2. Protocol.refer

獲取到服務(wù)地址列表后吟宦,接下來就是遍歷服務(wù)列表篮洁,調(diào)用 Protocol.refer 引入服務(wù):


image.png
image.png

如果獲取的服務(wù)地址數(shù)量大于 1,則調(diào)用完 refer 方法后殃姓,還要調(diào)用 Cluster.join 將多個(gè) invoker 合并成一個(gè) cluster invoker袁波,即集群類型,這部分邏輯在下面介紹蜗侈。

不管 url 的數(shù)量有多少篷牌,調(diào)用 refer 方法的邏輯都是一樣的。這里 Protocol 支持 SPI 擴(kuò)展踏幻,而 Protocol 接口的 Wrapper 類型的實(shí)現(xiàn)類枷颊,在服務(wù)導(dǎo)出時(shí)介紹過,多個(gè) Wrapper 類會形成 Protocol 調(diào)用鏈该面。

這里以未顯示設(shè)置 url 為例夭苗,即 urls 的數(shù)據(jù)類型都是注冊中心(協(xié)議頭為 registry),形成的 Protocol 調(diào)用鏈為:


image.png
image.png

前面示例中配置的協(xié)議為 "dubbo"隔缀,對應(yīng)的 Protocol 調(diào)用鏈為:


image.png
image.png

整體的流程如下:


image.png
image.png

主要分為以下幾個(gè)部分:

  1. 啟動 qos server题造;
  2. 注冊消費(fèi)者;
  3. 構(gòu)建路由策略鏈蚕泽;
  4. 訂閱數(shù)據(jù)
  5. notify
  6. 真正的服務(wù)引入
  7. 發(fā)布服務(wù)引入事件
  8. 構(gòu)建 filter 鏈
  9. 合并 invokers

其中 1 在 QosProtocolWrapper 類晌梨,2、3须妻、4仔蝌、5、9 在 RegistryProtocol 類荒吏,5 在 AbstractRegistry 類敛惊,6 在 DubboProtocol 類,7 在 ProtocolListenerWrapper 類绰更,8 在 ProtocolFilterWrapper 類瞧挤。

服務(wù)導(dǎo)出時(shí)也有 notify 操作,與服務(wù)導(dǎo)出相比儡湾,服務(wù)引入多了 構(gòu)建路由策略鏈特恬、合并 invokers 兩個(gè)步驟。

2.1 啟動 qos server

啟動 qos server 的邏輯與服務(wù)導(dǎo)出一致徐钠,依賴于 netty癌刽,啟動時(shí)注冊解碼器 QosProcessHandler 和監(jiān)聽 qos 端口,這里就不贅述:


image.png
image.png

2.2 注冊消費(fèi)者

接下來就進(jìn)入 RegistryProtocol.refer 的邏輯:


image.png
image.png

先轉(zhuǎn)換 URL 的協(xié)議頭,轉(zhuǎn)換結(jié)果如下:

// 轉(zhuǎn)換協(xié)議頭前的 url
registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=wlm
&dubbo=2.0.2&pid=85812&refer=application%3Dwlm%26check%3Dfalse%26dubbo%3D2.0.2%26init
%3Dtrue%26interface%3Dcom.wlm.dubbo.service.HelloWorld%26lazy%3Dfalse%26methods%3D
sayHello%26pid%3D85812%26protocol%3Ddubbo%26register.ip%3D192.168.199.243%26release
%3D2.7.3%26revision%3D1.0%26side%3Dconsumer%26sticky%3Dfalse%26timeout%3D600000%26
timestamp%3D1578817753832%26version%3D1.0&registry=zookeeper&release=2.7.3
&timestamp=1578817754368

// 轉(zhuǎn)換協(xié)議頭后的 url
zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=wlm
&dubbo=2.0.2&pid=85812&refer=application%3Dwlm%26check%3Dfalse%26dubbo%3D2.0.2%26init
%3Dtrue%26interface%3Dcom.wlm.dubbo.service.HelloWorld%26lazy%3Dfalse%26methods%3D
sayHello%26pid%3D85812%26protocol%3Ddubbo%26register.ip%3D192.168.199.243%26release
%3D2.7.3%26revision%3D1.0%26side%3Dconsumer%26sticky%3Dfalse%26timeout%3D600000%26
timestamp%3D1578817753832%26version%3D1.0&release=2.7.3&timestamp=1578817754368

然后調(diào)用 RegistryFactory 獲取注冊中心显拜,支持 SPI 擴(kuò)展衡奥,這里配置的是 "zookeeper",因此調(diào)用 ZookeeperRegistryFactory 創(chuàng)建注冊中心远荠,最終得到 ZookeeperRegistry 注冊中心矮固。

再接下來判斷是否配置了 group 屬性,會影響到后面 "2.9 合并invokers" 的實(shí)現(xiàn):如果配置了則傳入的 cluster 實(shí)現(xiàn)為 MergeableClusterInvoker譬淳,如果未配置則根據(jù) SPI 獲取档址,此處使用默認(rèn)實(shí)現(xiàn) FailoverCluster。

最后調(diào)用 doRefer 進(jìn)行服務(wù)引入:

image.png
image.png

這里關(guān)注注冊消費(fèi)者的實(shí)現(xiàn)瘦赫,其他實(shí)現(xiàn)在下文其他部分單獨(dú)介紹辰晕。

先根據(jù)注冊中心的屬性生成消費(fèi)者 url蛤迎,得到 subscribeUrl 如下:

consumer://192.168.199.243/com.wlm.dubbo.service.HelloWorld?application=wlm
&check=false&dubbo=2.0.2&init=true&interface=com.wlm.dubbo.service.HelloWorld
&lazy=false&methods=sayHello&pid=86042&protocol=dubbo&release=2.7.3&revision=1.0
&side=consumer&sticky=false&timeout=600000&timestamp=1578818975240&version=1.0

然后在 subscribeUrl 基礎(chǔ)上添加 category 屬性确虱,作為待注冊的消費(fèi)者 url,得到 registeredConsumerUrl:

consumer://192.168.199.243/com.wlm.dubbo.service.HelloWorld?application=wlm
&category=consumers&check=false&dubbo=2.0.2&init=true&interface=com.wlm.dubbo.service.HelloWorld
&lazy=false&methods=sayHello&pid=86042&protocol=dubbo&release=2.7.3&revision=1.0
&side=consumer&sticky=false&timeout=600000&timestamp=1578818975240&version=1.0

然后將 registeredConsumerUrl 作為 Registry.register 的入?yún)⑻骜桑瑒?chuàng)建 zookeeper 目錄節(jié)點(diǎn):

public void doRegister(URL url) {
    try {
        zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
    } catch (Throwable e) {
        throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
    }
}

最終消費(fèi)者地址注冊到 zookeeper 路徑:/dubbo/com.wlm.dubbo.service.HelloWorld/consumers/校辩,內(nèi)容如下:


image.png
image.png

2.3 構(gòu)建路由策略鏈

Dubbo 調(diào)用 RegistryDirectory.buildRouterChain 構(gòu)建路由策略鏈實(shí)例對象,入?yún)?subscribeUrl:

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

public static <T> RouterChain<T> buildChain(URL url) {
    return new RouterChain<>(url);
}

private RouterChain(URL url) {
    List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class)
        .getActivateExtension(url, (String[]) null);

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

    initWithRouters(routers);
}

在實(shí)例化 RouterChain 對象時(shí)辆童,通過路由策略工廠 RouterFactory 獲取路由策略宜咒,RouterFactory 支持 SPI 擴(kuò)展,通過 getActivateExtension 獲取到的默認(rèn)實(shí)現(xiàn)有四個(gè):


image.png
image.png

循環(huán)調(diào)用 RouterFactory.getRouter 得到的路由策略如下:


image.png
image.png

2.4 訂閱數(shù)據(jù)

接下來就是調(diào)用 RegistryDirectory.subscribe 訂閱數(shù)據(jù)把鉴,訂閱之前為 subscribeUrl 添加屬性 category=providers,configurators,routers故黑,得到入?yún)?url 如下:

consumer://192.168.199.243/com.wlm.dubbo.service.HelloWorld?application=wlm
&category=providers,configurators,routers&check=false&dubbo=2.0.2&init=true
&interface=com.wlm.dubbo.service.HelloWorld&lazy=false&methods=sayHello&pid=86252
&protocol=dubbo&release=2.7.3&revision=1.0&side=consumer&sticky=false&timeout=600000
&timestamp=1578819977201&version=1.0

RegistryDirectory.subscribe 的實(shí)現(xiàn)如下:

public void subscribe(URL url) {
    setConsumerUrl(url);
    // 訂閱配置中心
    CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this);
    serviceConfigurationListener = new ReferenceConfigurationListener(this, url);
    // 訂閱 url
    registry.subscribe(url, this);
}

RegistryDirectory 實(shí)現(xiàn)了 NotifyListener 接口,RegistryDirectory 的實(shí)例對象作為傳入 Registry.subscribe 方法的參數(shù)庭砍,在訂閱的數(shù)據(jù)發(fā)生變化時(shí)會被通知场晶,Registry 為前面獲取到的注冊中心實(shí)例 ZookeeperRegistry。

這里訂閱 url 數(shù)據(jù)和服務(wù)導(dǎo)出的實(shí)現(xiàn)也是一樣的:


image.png
image.png

區(qū)別在于 toCategoriesPath 獲取到的 category 目錄列表不一樣怠缸,此處為:

/dubbo/com.wlm.dubbo.service.HelloWorld/providers
/dubbo/com.wlm.dubbo.service.HelloWorld/configurators
/dubbo/com.wlm.dubbo.service.HelloWorld/routers

接下來遍歷 category 目錄列表诗轻,創(chuàng)建目錄節(jié)點(diǎn)并注冊 ChildListener 監(jiān)聽器。

這里先啟動了服務(wù)提供者揭北,因此 providers 目錄節(jié)點(diǎn)下有數(shù)據(jù)扳炬,最終得到的 urls 如下:

dubbo://192.168.199.243:20880/com.wlm.dubbo.service.HelloWorld?anyhost=true
&application=wlm&bean.name=com.wlm.dubbo.service.HelloWorld&deprecated=false
&dubbo=2.0.2&dynamic=true&generic=false&interface=com.wlm.dubbo.service.HelloWorld
&methods=sayHello&pid=77895&register=true&release=2.7.3&revision=1.0&service.filter=dubboFilter
&side=provider&timestamp=1578734146864&version=1.0

empty://192.168.199.243/com.wlm.dubbo.service.HelloWorld?application=wlm&category=configurators
&check=false&dubbo=2.0.2&init=true&interface=com.wlm.dubbo.service.HelloWorld&lazy=false
&methods=sayHello&pid=86460&protocol=dubbo&release=2.7.3&revision=1.0&side=consumer
&sticky=false&timeout=600000&timestamp=1578821094174&version=1.0

empty://192.168.199.243/com.wlm.dubbo.service.HelloWorld?application=wlm&category=routers
&check=false&dubbo=2.0.2&init=true&interface=com.wlm.dubbo.service.HelloWorld&lazy=false
&methods=sayHello&pid=86460&protocol=dubbo&release=2.7.3&revision=1.0&side=consumer
&sticky=false&timeout=600000&timestamp=1578821094174&version=1.0

如果某個(gè)服務(wù)沒有服務(wù)提供者,也是能進(jìn)行服務(wù)引入操作的搔体,因?yàn)檫@里會創(chuàng)建一個(gè) providers 目錄恨樟,并注冊監(jiān)聽器,后續(xù)服務(wù)提供者上線后疚俱,會調(diào)用 notify 通知消費(fèi)者劝术。

接下來主動觸發(fā) notify。

2.5 notify

notify 是 AbstractRegistry 定義的方法,當(dāng)服務(wù)提供者發(fā)生變化時(shí)夯尽,會調(diào)用該方法進(jìn)行通知:


image.png
image.png

先將 urls 轉(zhuǎn)換為 map 格式瞧壮,以 category 作為 key,此處轉(zhuǎn)換后的 result 數(shù)據(jù)如下:


image.png
image.png

然后遍歷 result匙握,調(diào)用 NotifyListener.notify 通知監(jiān)聽器咆槽。

前面介紹訂閱數(shù)據(jù)流程時(shí)說過,傳入的 NotifyListener 入?yún)⑹?RegistryDirectory 對象圈纺,notify 的實(shí)現(xiàn)如下:


image.png
image.png

先將 urls 進(jìn)行分類秦忿,目前有三種:configurators、routers蛾娶、providers灯谣。如果有數(shù)據(jù)的話,再將 url 數(shù)據(jù)轉(zhuǎn)換成對應(yīng)的內(nèi)部對象蛔琅,添加到本地屬性中胎许。

這里重點(diǎn)關(guān)注 providers 分類的 url 數(shù)據(jù),調(diào)用 refreshOverrideAndInvoker 方法轉(zhuǎn)換成 invoker 的邏輯罗售。

前面展示的 urls 中辜窑,有些協(xié)議頭為 "empty" 的,是無效 url寨躁,因此在轉(zhuǎn)換 url 的過程中穆碎,會被跳過。

最終調(diào)用 Protocol.refer 引入服務(wù)职恳,此處協(xié)議頭為 "dubbo"所禀,因此會調(diào)用 DubboProtocol:


image.png
image.png

這里生成的 key 為 url 的 fullString:

dubbo://192.168.199.243:20880/com.wlm.dubbo.service.HelloWorld?anyhost=true
&application=wlm&bean.name=com.wlm.dubbo.service.HelloWorld&check=false&deprecated=false
&dubbo=2.0.2&dynamic=true&generic=false&init=true&interface=com.wlm.dubbo.service.HelloWorld
&lazy=false&methods=sayHello&pid=86862&protocol=dubbo&register=true&register.ip=192.168.199.243
&release=2.7.3&remote.application=wlm&revision=1.0&service.filter=dubboFilter&side=consumer
&sticky=false&timeout=600000&timestamp=1578734146864&version=1.0

2.6 真正的服務(wù)引入

DubboProtocol 繼承了 AbstractProtocol,AbstractProtocol 調(diào)用子類實(shí)現(xiàn)的 protocolBindingRefer 方法放钦,并將結(jié)果封裝在 AsyncToSyncInvoker 返回:

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    return new AsyncToSyncInvoker<>(protocolBindingRefer(type, url));
}

protected abstract <T> Invoker<T> protocolBindingRefer(Class<T> type, URL url) throws RpcException;

DubboProtocol 的實(shí)現(xiàn)如下:

public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
    optimizeSerialization(url);

    // create rpc invoker.
    DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
    invokers.add(invoker);

    return invoker;
}

Dubbo 調(diào)用 getClients 獲取和服務(wù)端的連接色徘,并封裝成 DubboInvoker 對象返回。

Dubbo 中的連接分為共享連接和獨(dú)立連接最筒,如果配置了 connections 屬性贺氓,則使用獨(dú)立連接,否則使用共享連接床蜘。有關(guān)這部分后續(xù)單獨(dú)介紹辙培,這里重點(diǎn)關(guān)注如何建立連接,也就是 initClient 的實(shí)現(xiàn)邢锯。

initClient 的時(shí)序圖如下:


image.png
image.png

Exchanger扬蕊、Transporter、Client 都支持 SPI 擴(kuò)展丹擎,相關(guān)的概念已經(jīng)在介紹服務(wù)導(dǎo)出流程時(shí)解釋過尾抑,此處不再贅述歇父。

此處使用了 Dubbo 的默認(rèn)的傳輸協(xié)議 netty,對應(yīng) NettyClient再愈,在實(shí)例化時(shí)會建立和服務(wù)端的連接榜苫。

先調(diào)用 doOpen 初始化,注冊編解碼器 NettyCodecAdapter(和服務(wù)導(dǎo)出一致):

image.png
image.png

再調(diào)用 doConnect 建立連接翎冲,host 和 port 都從服務(wù)提供者 url 中獲却共恰:


image.png
image.png

2.7 發(fā)布服務(wù)引入事件

ProtocolListenerWrapper 調(diào)用完 refer 方法后,會返回 ListenerInvokerWrapper 包裝類的實(shí)例對象抗悍,通過 Dubbo SPI 機(jī)制獲取監(jiān)聽器 InvokerListener 列表驹饺,作為入?yún)鬟f到 ListenerInvokerWrapper 的構(gòu)造器中:


image.png
image.png

并在對象構(gòu)造器內(nèi)調(diào)用 InvokerListener.referred() 發(fā)布服務(wù)引入事件:


image.png
image.png

2.8 構(gòu)建 filter 鏈

接下來進(jìn)入 ProtocolFilterWrapper 構(gòu)建 filter 鏈的邏輯:

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
        return protocol.refer(type, url);
    }
    return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
}

這里與服務(wù)導(dǎo)出時(shí)構(gòu)建 filter 鏈的邏輯一致,就不贅述缴渊,區(qū)別在于此處傳入的 key=reference.filter赏壹,group=consumer。

默認(rèn)的服務(wù)消費(fèi)者 filter 鏈如下:


image.png
image.png

2.9 合并 invokers

到這里衔沼,RegistryDirectory.subscribe 的邏輯執(zhí)行完成蝌借,RegistryDirectory 包含了注冊中心地址、注冊中心內(nèi)的服務(wù)提供者 invoker 列表俐巴、路由策略鏈等數(shù)據(jù):


image.png
image.png

服務(wù)提供者 invoker 也是一個(gè)嵌套的結(jié)構(gòu)骨望,從上到下對應(yīng):ConsumerContextFilter -> FutureFilter -> MonitorFilter -> AsyncToSyncInvoker -> DubboInvoker硬爆,而 DubboInvoker 內(nèi)包含了和服務(wù)端的連接 ExchangeClient 數(shù)組:


image.png
image.png

Dubbo 調(diào)用 Cluster.join 將該 Directory 對象內(nèi)的多個(gè) invokers 合并在一起欣舵,Cluster 支持 SPI 擴(kuò)展,最終得到一個(gè)嵌套的 invoker 對象:MockClusterInvoker -> FailoverClusterInvoker缀磕。

3. 合并 invokers

如果前面獲取服務(wù)地址列表是缘圈,得到的地址數(shù)量 > 1,則會進(jìn)入合并 invokers 的處理:


image.png
image.png

這里的合并 invokers 與前面 Protocol.refer 的區(qū)別在于:

  • Protocol.refer 針對的是某一個(gè) url袜蚕,如果 url 類型是注冊中心糟把,最終會得到一批服務(wù)提供者 invoker 列表,因此該合并針對的是同一個(gè)注冊中心牲剃;
  • 此處 url 有多個(gè)時(shí)遣疯,針對每個(gè) url 執(zhí)行 Protocol.refer 都會得到一個(gè) invoker 對象,因此該合并針對的是多注冊中心或多 url凿傅。

合并的實(shí)現(xiàn)都是基于 Cluster.join 接口缠犀,就不贅述。

4. 創(chuàng)建代理

前面的過程主要是把服務(wù)提供者轉(zhuǎn)換成 invoker 對象聪舒,而這里是將 invoker 對象轉(zhuǎn)換為服務(wù)的本地代理對象辨液。

ProxyFactory 支持 SPI 擴(kuò)展,默認(rèn)獲取到的是一個(gè) ProxyFactory 鏈:StubProxyFactoryWrapper -> JavassistProxyFactory箱残。

JavassistProxyFactory 依賴于 javassist 組件:

public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
    return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

先根據(jù)引用的服務(wù)的 Class 對象創(chuàng)建代理 Proxy 對象滔迈,再將 invoker 對象封裝到 InvokerInvocationHandler 對象止吁,作為 Proxy 對象實(shí)例化的入?yún)ⅰ?/p>

生成動態(tài)代理類的實(shí)現(xiàn)不是本文重點(diǎn)宝与,感興趣的讀者自行了解 javassist 動態(tài)生成代理類的過程茫舶。

總結(jié)

本篇文章側(cè)重于 Dubbo 服務(wù)引入的實(shí)現(xiàn)細(xì)節(jié)客峭,主要包括:服務(wù)引入入口犁苏,獲取服務(wù)地址列表灸眼,啟動 qos server南窗,注冊消費(fèi)者耙厚,構(gòu)建路由策略鏈渠缕,訂閱數(shù)據(jù)勾哩,notify抗蠢,服務(wù)引入,發(fā)布服務(wù)引入事件思劳,構(gòu)建 filter 鏈迅矛,合并 invokers 等。其中省略了很多細(xì)節(jié)潜叛,限于篇幅秽褒,讀者可自行查看。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末威兜,一起剝皮案震驚了整個(gè)濱河市销斟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌椒舵,老刑警劉巖蚂踊,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異笔宿,居然都是意外死亡犁钟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門泼橘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涝动,“玉大人,你說我怎么就攤上這事炬灭〈姿冢” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵重归,是天一觀的道長米愿。 經(jīng)常有香客問我,道長提前,這世上最難降的妖魔是什么吗货? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮狈网,結(jié)果婚禮上宙搬,老公的妹妹穿的比我還像新娘笨腥。我一直安慰自己,他們只是感情好勇垛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布脖母。 她就那樣靜靜地躺著,像睡著了一般闲孤。 火紅的嫁衣襯著肌膚如雪谆级。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天讼积,我揣著相機(jī)與錄音肥照,去河邊找鬼。 笑死勤众,一個(gè)胖子當(dāng)著我的面吹牛舆绎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播们颜,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼吕朵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了窥突?” 一聲冷哼從身側(cè)響起努溃,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎阻问,沒想到半個(gè)月后梧税,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡则拷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年贡蓖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片煌茬。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖彻桃,靈堂內(nèi)的尸體忽然破棺而出坛善,到底是詐尸還是另有隱情,我是刑警寧澤邻眷,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布眠屎,位于F島的核電站,受9級特大地震影響肆饶,放射性物質(zhì)發(fā)生泄漏改衩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一驯镊、第九天 我趴在偏房一處隱蔽的房頂上張望葫督。 院中可真熱鬧竭鞍,春花似錦、人聲如沸橄镜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洽胶。三九已至晒夹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間姊氓,已是汗流浹背丐怯。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翔横,地道東北人响逢。 一個(gè)月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像棕孙,于是被迫代替她去往敵國和親舔亭。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 先看官網(wǎng)兩張圖【引用來自官網(wǎng)】:image.png 官網(wǎng)說明: 1.首先 ReferenceConfig 類的 i...
    致慮閱讀 1,027評論 0 2
  • dubbo暴露服務(wù)有兩種情況蟀俊,一種是設(shè)置了延遲暴露(比如delay="5000")钦铺,另外一種是沒有設(shè)置延遲暴露或者...
    加大裝益達(dá)閱讀 21,270評論 5 36
  • 前言 本文繼續(xù)分析dubbo的cluster層,此層封裝多個(gè)提供者的路由及負(fù)載均衡肢预,并橋接注冊中心矛洞,以Invoke...
    Java大生閱讀 991評論 0 0
  • 服務(wù)發(fā)布分析完了,下面讓我們開始服務(wù)消費(fèi)端烫映。首先看下官方的時(shí)序圖沼本,有個(gè)總體印象 根據(jù)上一篇服務(wù)發(fā)布的邏輯,先看我們...
    愛編程的凱哥閱讀 1,102評論 0 1
  • 上一篇我們介紹了暴露服務(wù)锭沟,這一篇我們來說引用服務(wù)抽兆。首先我們看下應(yīng)用層引用服務(wù)所做的 在使用的地方,聲明了一個(gè)Ref...
    數(shù)齊閱讀 3,242評論 0 3