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)目啟動(dòng)的時(shí)候就會(huì)執(zhí)行服務(wù)引入邏輯。

服務(wù)引入入口

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

ReferenceBean 實(shí)現(xiàn)了 InitializingBean 接口,因此在 bean 初始化時(shí)會(huì)被調(diào)用 afterPropertiesSet() 方法。ReferenceBean 的實(shí)現(xiàn)中,首先針對(duì)各類型的配置,判斷未配置時(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)部對(duì)相關(guān)的配置進(jìn)行檢查和覆蓋;再判斷 ref 屬性是否為 null,也就是是否執(zhí)行過(guò)服務(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 對(duì)象中:


image.png
image.png

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

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

1.通過(guò) 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 對(duì)原型的定義和 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;
}

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


image.png
image.png

這里主要邏輯為:

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

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


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 對(duì)象;
  3. 如果得到多個(gè) invoker 對(duì)象,則調(diào)用 Cluster.join 將它們合并為一個(gè) invoker 對(duì)象;
  4. 調(diào)用 ProxyFactory.getProxy 創(chuàng)建代理,將 invoker 對(duì)象轉(zhuǎn)換為 interface 對(duì)應(yīng)的 Proxy 對(duì)象。

接下來(lái)就看看這四個(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ù)的地址,也可以是注冊(cè)中心的地址,根據(jù)協(xié)議頭區(qū)分。

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

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

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

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

2. Protocol.refer

獲取到服務(wù)地址列表后,接下來(lái)就是遍歷服務(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í)介紹過(guò),多個(gè) Wrapper 類會(huì)形成 Protocol 調(diào)用鏈。

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


image.png
image.png

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


image.png
image.png

整體的流程如下:


image.png
image.png

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

  1. 啟動(dòng) qos server;
  2. 注冊(cè)消費(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 啟動(dòng) qos server

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


image.png
image.png

2.2 注冊(cè)消費(fèi)者

接下來(lá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 獲取注冊(cè)中心,支持 SPI 擴(kuò)展,這里配置的是 "zookeeper",因此調(diào)用 ZookeeperRegistryFactory 創(chuàng)建注冊(cè)中心,最終得到 ZookeeperRegistry 注冊(cè)中心。

再接下來(lái)判斷是否配置了 group 屬性,會(huì)影響到后面 "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)注注冊(cè)消費(fèi)者的實(shí)現(xiàn),其他實(shí)現(xiàn)在下文其他部分單獨(dú)介紹。

先根據(jù)注冊(cè)中心的屬性生成消費(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 屬性,作為待注冊(cè)的消費(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)者地址注冊(cè)到 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í)例對(duì)象,入?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 對(duì)象時(shí),通過(guò)路由策略工廠 RouterFactory 獲取路由策略,RouterFactory 支持 SPI 擴(kuò)展,通過(guò) 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ù)

接下來(lái)就是調(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í)例對(duì)象作為傳入 Registry.subscribe 方法的參數(shù),在訂閱的數(shù)據(jù)發(fā)生變化時(shí)會(huì)被通知,Registry 為前面獲取到的注冊(cè)中心實(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

接下來(lái)遍歷 category 目錄列表,創(chuàng)建目錄節(jié)點(diǎn)并注冊(cè) ChildListener 監(jiān)聽(tīng)器。

這里先啟動(dòng)了服務(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ù)沒(méi)有服務(wù)提供者,也是能進(jìn)行服務(wù)引入操作的,因?yàn)檫@里會(huì)創(chuàng)建一個(gè) providers 目錄,并注冊(cè)監(jiān)聽(tīng)器,后續(xù)服務(wù)提供者上線后,會(huì)調(diào)用 notify 通知消費(fèi)者。

接下來(lái)主動(dòng)觸發(fā) notify。

2.5 notify

notify 是 AbstractRegistry 定義的方法,當(dāng)服務(wù)提供者發(fā)生變化時(shí),會(huì)調(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)聽(tīng)器。

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


image.png
image.png

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

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

前面展示的 urls 中,有些協(xié)議頭為 "empty" 的,是無(wú)效 url,因此在轉(zhuǎn)換 url 的過(guò)程中,會(huì)被跳過(guò)。

最終調(diào)用 Protocol.refer 引入服務(wù),此處協(xié)議頭為 "dubbo",因此會(huì)調(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 對(duì)象返回。

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í)解釋過(guò),此處不再贅述。

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

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

image.png
image.png

再調(diào)用 doConnect 建立連接,host 和 port 都從服務(wù)提供者 url 中獲?。?/p>

image.png
image.png

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

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


image.png
image.png

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


image.png
image.png

2.8 構(gòu)建 filter 鏈

接下來(lái)進(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 包含了注冊(cè)中心地址、注冊(cè)中心內(nèi)的服務(wù)提供者 invoker 列表、路由策略鏈等數(shù)據(jù):


image.png
image.png

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


image.png
image.png

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

3. 合并 invokers

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


image.png
image.png

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

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

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

4. 創(chuàng)建代理

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

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 對(duì)象創(chuàng)建代理 Proxy 對(duì)象,再將 invoker 對(duì)象封裝到 InvokerInvocationHandler 對(duì)象,作為 Proxy 對(duì)象實(shí)例化的入?yún)ⅰ?/p>

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

總結(jié)

本篇文章側(cè)重于 Dubbo 服務(wù)引入的實(shí)現(xiàn)細(xì)節(jié),主要包括:服務(wù)引入入口,獲取服務(wù)地址列表,啟動(dòng) qos server,注冊(cè)消費(fèi)者,構(gòu)建路由策略鏈,訂閱數(shù)據(jù),notify,服務(wù)引入,發(fā)布服務(wù)引入事件,構(gòu)建 filter 鏈,合并 invokers 等。其中省略了很多細(xì)節(jié),限于篇幅,讀者可自行查看。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容