Dubbo之服務引用源碼分析

服務引用有兩種:
1、直連方式引用
2、基于注冊中心引用

引用服務時機

1、 ReferenceBean 的 afterPropertiesSet 方法時引用服務
org.apache.dubbo.config.spring.ReferenceBean#afterPropertiesSet
由于ReferenceBean實現(xiàn)了InitializingBean,這里是spring bean生命周期中,調(diào)用afterPropertiesSet執(zhí)行初始化方法的一個切入點。


image.png

調(diào)用shouldInit方法查看init參數(shù)是否為true,如果為true將會在這里初始化。默認是false
2、 ReferenceBean 對應的服務提供者類被注入到服務消費者類中的引用(默認情況)
org.apache.dubbo.config.ReferenceConfig#get
其主要邏輯是在init方法,經(jīng)歷獲取服務配置、Invoker 創(chuàng)建、代理類創(chuàng)建等步驟。


image.png

init

org.apache.dubbo.config.ReferenceConfig#init


image.png

1、檢查本地存根checkStubAndLocal
2、檢查Mock checkMock


image.png

3、添加 side、協(xié)議版本信息、時間戳和進程號等信息到 map 中,如果不是泛化服務將獲取版本,獲取接口方法列表,并添加到 map 中,將 ApplicationConfig、ConsumerConfig、ReferenceConfig 等對象的字段信息添加到 map 中
image.png

4、methods參數(shù)配置處理,


image.png

5、獲取服務消費者 ip 地址、存儲 attributes 到系統(tǒng)上下文中
image.png

6、調(diào)用createProxy創(chuàng)建代理類ref

引用服務

org.apache.dubbo.config.ReferenceConfig#createProxy


image.png

1、本地JVM引用


image.png

2、url不為空,則是點對點的服務地址。@Reference中指定了url屬性
image.png

3、如果是注冊中心地址,則在url中添加一個refer參數(shù)。
首先獲取注冊url。然后把引入的map數(shù)據(jù)屬性放到refer后與注冊url拼接,并添加到urls中。
如果是服務地址,有可能url中配置了參數(shù),map中表示的服務消費者消費服務時的參數(shù),這里所以需要合并mergeUrl

registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-consumer-application&dubbo=2.0.2&pid=9484&refer=application%3Ddubbo-demo-consumer-application%26dubbo%3D2.0.2%26group%3Dg1%26interface%3Dorg.apache.dubbo.demo.DemoService%26lazy%3Dfalse%26methods%3DsayHello%26pid%3D9484%26register.ip%3D192.168.1.2%26release%3D2.7.0%26revision%3D1.1.1%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1596342012598%26version%3D1.1.1&registry=zookeeper&release=2.7.0&timestamp=1596342266342
image.png

4、@Reference中的protocol屬性表示使用哪個協(xié)議調(diào)用服務,如果不是本地調(diào)用協(xié)議,則把注冊中心地址找出來
如果只有一個注冊服務url,則直接調(diào)用Protocol的自適應類,調(diào)用ref引用方法,這里會調(diào)用到RegistryProtocol、DubboProtocol


image.png

5、如果有多個注冊服務url,則會進行遍歷urls。生成的執(zhí)行體invoker添加到invokers集合中。并對最后一個registryURL,進行Cluster包裹


image.png

6、得到執(zhí)行體invoker,并通過ProxyFactory生成代理類
生成的invoker實例,就是為了調(diào)用。這里最終的包裹
image.png

MockClustInvoker包裹服務字典RegistryDirectory和集群容錯Cluster的FailoverClusterInvoker,F(xiàn)ailoverClusterInvoker也包裹了服務字典RegistryDirectory

所以說,在根據(jù)PROXY_FACTORY.getProxy獲取invoker的代理類之前,生成invoker才是代理類的核心。

動態(tài)服務目錄

RegistryProtocol的包裹類ProtocolFilterWrapper和ProtocolListenerWrapper(最外包裹)就不看了,也是利用SPI
1、refer
org.apache.dubbo.registry.integration.RegistryProtocol#refer


image.png

①、把url地址由registry://開頭轉換成 zookeeper://開頭

zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-consumer-application&dubbo=2.0.2&pid=10732&refer=application%3Ddubbo-demo-consumer-application%26dubbo%3D2.0.2%26group%3Dg1%26interface%3Dorg.apache.dubbo.demo.DemoService%26lazy%3Dfalse%26methods%3DsayHello%26pid%3D10732%26register.ip%3D192.168.1.2%26release%3D2.7.0%26revision%3D1.1.1%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1596345426916%26version%3D1.1.1&release=2.7.0&timestamp=1596345429789

②、從url獲取注冊類registry


image.png

③、 qs表示 查詢字符, 表示url中的參數(shù),表示消費者引入服務所配置的參數(shù)。如果有group有多個值,則這里的cluster為MergeableCluster
④、doRefer方法調(diào)用
2、doRefer
org.apache.dubbo.registry.integration.RegistryProtocol#doRefer


image.png

image.png

①、創(chuàng)建動態(tài)服務目錄動態(tài)RegistryDirectory,并設置registry、protocol值。RegistryDirectory實現(xiàn)了 NotifyListener 接口。當注冊中心服務配置發(fā)生變化后,RegistryDirectory 可收到與當前服務相關的變化。收到變更通知后,RegistryDirectory 可根據(jù)配置變更信息刷新 Invoker 列表。在服務消費端最核心的就是這個服務目錄

②、獲取訂閱subscribeUrl、并注冊到zookeeper

consumer://192.168.1.2/org.apache.dubbo.demo.DemoService?application=dubbo-demo-consumer-application&dubbo=2.0.2&group=g1&interface=org.apache.dubbo.demo.DemoService&lazy=false&methods=sayHello&pid=10732&release=2.7.0&revision=1.1.1&side=consumer&sticky=false&timestamp=1596345426916&version=1.1.1

a、register
org.apache.dubbo.registry.support.FailbackRegistry#register


image.png

b、doRegister
org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doRegister


image.png

注冊路徑是:
/dubbo/org.apache.dubbo.demo.DemoService/consumers/consumer%3A%2F%2F192.168.1.2%2Forg.apache.dubbo.demo.DemoService%3Fapplication%3Ddubbo-demo-consumer-application%26category%3Dconsumers%26check%3Dfalse%26dubbo%3D2.0.2%26group%3Dg1%26interface%3Dorg.apache.dubbo.demo.DemoService%26lazy%3Dfalse%26methods%3DsayHello%26pid%3D10732%26release%3D2.7.0%26revision%3D1.1.1%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1596345426916%26version%3D1.1.1

③、構建路由鏈
④、服務目錄需要訂閱的幾個路徑
⑤、cluster包裹服務目錄

構建路由鏈

路由鏈是動態(tài)服務目錄的一個屬性值,在引入服務時可以根據(jù)路由條件進行過濾
org.apache.dubbo.registry.integration.RegistryDirectory#buildRouterChain


image.png

org.apache.dubbo.rpc.cluster.RouterChain#buildChain


image.png

org.apache.dubbo.rpc.cluster.RouterChain#RouterChain
image.png

根據(jù)路由工廠RouterFactory接口的擴展實現(xiàn)類,得到四個:

MockRouterFactory、TagRouterFactory 標簽路由、AppRouterFactory應用條件路由、ServiceRouterFactory服務條件路由
利用RouterFactory根據(jù)url生成各個類型的Router、并把routers按priority進行排序
MockInvokersSelector(priority=負最大整數(shù))、TagRouter(priority=100)、ServiceRouter(priority=140)、AppRouter(priority=150)

服務目錄訂閱路徑

服務目錄需要訂閱的幾個路徑
服務提供者目錄:/dubbo/org.apache.dubbo.demo.DemoService/providers
老版本動態(tài)配置目錄:/dubbo/org.apache.dubbo.demo.DemoService/configurators
老版本路由器目錄:/dubbo/org.apache.dubbo.demo.DemoService/routers
0、subscribe
org.apache.dubbo.registry.integration.RegistryDirectory#subscribe


image.png

此時有url多了category=providers,configurators,routers參數(shù)

consumer://192.168.1.2/org.apache.dubbo.demo.DemoService?application=dubbo-demo-consumer-application&category=providers,configurators,routers&dubbo=2.0.2&group=g1&interface=org.apache.dubbo.demo.DemoService&lazy=false&methods=sayHello&pid=10224&release=2.7.0&revision=1.1.1&side=consumer&sticky=false&timestamp=1596347742946&version=1.1.1

1、ConsumerConfigurationListener監(jiān)聽器,
ConsumerConfigurationListener是RegistryDirectory的一個屬性值,在創(chuàng)建RegistryDirectory對象時創(chuàng)建


image.png

key是dubbo-demo-consumer-application.configurators,所以監(jiān)聽應用動態(tài)配置路徑是/dubbo/config/dubbo/dubbo-demo-consumer-application.configurators
2、ReferenceConfigurationListener監(jiān)聽器


image.png

key是org.apache.dubbo.demo.DemoService:1.1.1:g1.configurators,所以監(jiān)聽路徑服務動態(tài)配置目錄是/dubbo/config/dubbo/org.apache.dubbo.demo.DemoService:1.1.1:g1.configurators
3、subscribe
org.apache.dubbo.registry.support.FailbackRegistry#subscribe
image.png

①、subscribe
org.apache.dubbo.registry.support.FailbackRegistry#subscribe


image.png

②、doSubscribe
org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doSubscribe
a、首先判斷服務接口是不是*,這里不會走
image.png

image.png

b、走else邏輯
從消費者url根據(jù)category獲取路徑(category=providers,configurators,routers)
image.png

得到服務提供者端配置路徑(providers服務提供者、configurators服務提供者配置路徑、routers路由配置路徑)
/dubbo/org.apache.dubbo.demo.DemoService/providers
/dubbo/org.apache.dubbo.demo.DemoService/configurators

/dubbo/org.apache.dubbo.demo.DemoService/routers


image.png

對下面三個路徑進行遍歷,并創(chuàng)建路徑,在添加urls之前,會首先通過toUrlsWithEmpty進行Empty協(xié)議替換。
image.png

c、toUrlsWithEmpty
org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#toUrlsWithEmpty
為空,則用
image.png

d、toUrlsWithoutEmpty
對引入/dubbo/org.apache.dubbo.demo.DemoService/providers所有服務提供者進行遍歷
org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#toUrlsWithoutEmpty
image.png

e、isMatch
org.apache.dubbo.common.utils.UrlUtils#isMatch
服務提供者與消費者引入是否匹配
image.png

首先根據(jù)consumerInterface和providerInterface
image.png

根據(jù)category、根據(jù)enabled,默認是true
consumerUrl中category=providers,configurators,routers
providerUrl 得到默認category=providers
consumerUrl = "consumer://192.168.1.2/org.apache.dubbo.demo.DemoService?application=dubbo-demo-consumer-application&category=providers,configurators,routers&dubbo=2.0.2&group=g1&interface=org.apache.dubbo.demo.DemoService&lazy=false&methods=sayHello&pid=9320&release=2.7.0&revision=1.1.1&side=consumer&sticky=false&timestamp=1596349730827&version=1.1.1"
providerUrl =  "dubbo://192.168.1.2:20881/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-provider1-application&bean.name=ServiceBean:org.apache.dubbo.demo.DemoService:1.1.1:g1&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=g1&interface=org.apache.dubbo.demo.DemoService&logger=log4j&methods=sayHello&pid=9596&release=2.7.0&revision=1.1.1&sayHello.loadbalance=random&sayHello.return=true&side=provider&timeout=3000&timestamp=1596340240853&version=1.1.1"

根據(jù)group、version、classifier。三個同時符合則返回true


image.png

③、notify
org.apache.dubbo.registry.support.FailbackRegistry#notify


image.png

a、doNotify
org.apache.dubbo.registry.support.FailbackRegistry#doNotify
image.png

b、notify
org.apache.dubbo.registry.support.AbstractRegistry#notify
分類category


image.png

image.png

c、對routers、configurators、providers調(diào)用notify方法
org.apache.dubbo.registry.integration.RegistryDirectory#notify
image.png

獲取動態(tài)配置URL,生成configurators、獲取老版本路由URL,生成Router,并添加到路由鏈中、獲取服務提供者URL
d、refreshOverrideAndInvoker
這里只要看providers,根據(jù)動態(tài)配置手動觸發(fā)一次刷新invoker。
org.apache.dubbo.registry.integration.RegistryDirectory#refreshOverrideAndInvoker
image.png

e、refreshInvoker
org.apache.dubbo.registry.integration.RegistryDirectory#refreshInvoker
這里會Protocol進行過濾,并且調(diào)用DubboProtocol.refer方法得到Invoker


image.png

image.png

得到的newInvokers設置到RegistryDirectory的routerChain和invokers中
f、toInvokers
org.apache.dubbo.registry.integration.RegistryDirectory#toInvokers
遍歷當前服務所有的服務提供者URL、當前消費者如果手動配置了Protocol,那么則進行匹配
image.png

當前Protocol是否在應用中存在對應的擴展點
image.png

如果當前服務提供者URL緩存中l(wèi)ocalUrlInvokerMap沒有,則之前沒有生產(chǎn)過Invoker。則會調(diào)用Protocol的refer方法生成一個Invoker
image.png

會調(diào)用到DubboProtocol#refer方法,DubboProtocol繼承了AbstractProtocol,DubboProtocol不存在refer方法。會調(diào)用到父類AbstractProtocol的refer方法
客戶端ExchangeClient創(chuàng)建
DubboInvoker創(chuàng)建

1、refer
org.apache.dubbo.rpc.protocol.AbstractProtocol#refer
DubboInvoker是異步的,而AsyncToSyncInvoker會封裝為同步的


image.png

2、protocolBindingRefer
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#protocolBindingRefer


image.png

通過getClients(url)方法獲取ExchangeClient客戶端。這里是個數(shù)組形式, 在DubboInvoker發(fā)送請求時會輪詢clients去發(fā)送數(shù)據(jù),如果有多個的話。ExchangeClient 實際上并不具備通信能力,它需要基于更底層的客戶端實例進行通信例如NettyClient
3、getClients
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#getClients
image.png

connections表示建立幾個socket連接,在DubboProtocol中,每構造一個Client就會去和Server建立一個Socket連接
如果connections為0,這時會去獲取shareconnections參數(shù),默認為1。默認情況下,使用共享客戶端實例。如果不使用共享客戶端會調(diào)用initClient方法,getSharedClient內(nèi)部也會調(diào)用initClient
4、getSharedClient
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#getSharedClient


image.png

image.png

image.png

5、buildReferenceCountExchangeClientList
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#buildReferenceCountExchangeClientList
image.png

6、buildReferenceCountExchangeClient
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#buildReferenceCountExchangeClient
image.png

initClient生成一個ExchangeClient,并用ReferenceCountExchangeClient包裝。ReferenceCountExchangeClient是引用計數(shù)功能的 ExchangeClient
7、initClient
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#initClient
image.png

從url得到client值,默認為netty。添加編解碼和心跳包參數(shù)到 url 中,編碼方式是DubboCodec.NAME、心跳heartbeat值是60 * 1000ms。并檢測客戶端類型是否存在,不存在則拋出異常。


image.png

獲取 lazy 配置,并根據(jù)配置值決定創(chuàng)建的客戶端類型,LazyConnectExchangeClient
Exchangers建立連接,把url和requestHandler傳入。requestHandler是DubboProtocol的內(nèi)部類
8、connect
org.apache.dubbo.remoting.exchange.Exchangers#connect
得到一個HeaderExchanger(Exchanger的SPI默認是header)去connect
image.png

9、connect
org.apache.dubbo.remoting.exchange.support.header.HeaderExchanger#connect
這里進行以下調(diào)用,分別如下:
創(chuàng)建HeaderExchangeHandler 對象、 創(chuàng)建 DecodeHandler 對象、通過 Transporters 構建 Client 實例、 創(chuàng)建 HeaderExchangeClient 對象。
用NettyTransporter去connect。DecodeHandler解碼,是把InputStream解析成AppResponse對象
image.png

10、connect
org.apache.dubbo.remoting.Transporters#connect
image.png

11、connect
org.apache.dubbo.remoting.transport.netty4.NettyTransporter#connect
image.png

12、NettyClient
org.apache.dubbo.remoting.transport.netty4.NettyClient#NettyClient
初始化和開始netty
image.png

13、AbstractClient
org.apache.dubbo.remoting.transport.AbstractClient#AbstractClient
image.png

14、doOpen
org.apache.dubbo.remoting.transport.netty4.NettyClient#doOpen
netty啟動
image.png

15、connect

org.apache.dubbo.remoting.transport.AbstractClient#connect


image.png

16、doConnect
org.apache.dubbo.remoting.transport.netty4.NettyClient#doConnect
image.png

所以得到的DubboInvoker結構是
image.png

MockClusterInvoker
?RegistryDirectory
??Collections$UnmodifiableRandomAccessList
?routerChain
??MockInvokersSelector
??TagRouter
??AppRouter
?? ServiceRouter

Collections$UnmodifiableRandomAccessList
?RegistryDirectory$InvokerDelegate
??ListenerInvokerWrapper
???ProtocolFilterWrapper$CallbackRegistrationInvoker
????ProtocolFilterWrapper$1
?????AsyncToSyncInvoker
??????DubboInvoker

DubboInvoker
?ExchangeClient[1]
??ReferenceCountExchangeClient
???HeaderExchangeClient
????NettyClient

創(chuàng)建代理

PROXY_FACTORY.getProxy(invoker)
1、getProxy
org.apache.dubbo.rpc.proxy.wrapper.StubProxyFactoryWrapper#getProxy(org.apache.dubbo.rpc.Invoker<T>)


image.png

2、getProxy
org.apache.dubbo.rpc.proxy.AbstractProxyFactory#getProxy(org.apache.dubbo.rpc.Invoker<T>)


image.png

3、getProxy
org.apache.dubbo.rpc.proxy.AbstractProxyFactory#getProxy(org.apache.dubbo.rpc.Invoker<T>, boolean)
image.png

image.png

4、getProxy
org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory#getProxy


image.png

invoker通過InvokerInvocationHandler包裝 最終生成的代理類
這里也是調(diào)用的開始,通過sayHello方法會執(zhí)行InvocationHandler handler的invoke方法
package org.apache.dubbo.common.bytecode;

public class proxy0 implements org.apache.dubbo.demo.DemoService {

    public static java.lang.reflect.Method[] methods;

    private java.lang.reflect.InvocationHandler handler;

    public proxy0() {
    }

    public proxy0(java.lang.reflect.InvocationHandler arg0) {
        handler = $1;
    }
    public java.lang.String sayHello(java.lang.String arg0) {
        Object[] args = new Object[1];
        args[0] = ($w) $1;
        Object ret = handler.invoke(this, methods[0], args);
        return (java.lang.String) ret;
    }
}

總結

本片主要分析了dubbo消費端的從注冊中心引入的過程。需要明白一點,引入是為調(diào)用做準備的。
引入層級較為多,其核心思想就是Invoker的生成。最終ref引入的是Invoker經(jīng)過InvokerInvocationHandler包裝的代理類,通過Javassist生成。
其中,Invoker是通過Protocol.ref生成。具體Protocol通過SPI原理實現(xiàn),首先經(jīng)過RegistryProtocol#refer、再經(jīng)過DubboProtocol#refer方法。
在RegistryProtocol中主要是生成動態(tài)服務目錄RegistryDirectory,并為RegistryDirectory設置registry、protocol、消費url等,并設置路由鏈。由于服務目錄實現(xiàn)了NotifyListener,是個監(jiān)聽器。需要對新老版本的服務動態(tài)配置路徑及服務配置路徑、路由等的監(jiān)聽。動態(tài)服務目錄RegistryDirectory訂閱后,會直接觸發(fā)一次,這次會調(diào)用到DubboProtocol的refer方法。最終得到的RegistryDirectory需要cluster進行包裝。
在DubboProtocol引入中,主要是對客戶端實例的獲取。涉及到Exchanger 、傳輸協(xié)議Transporter等SPI,最終會調(diào)用NettyClient創(chuàng)建netty(默認)客戶端,并進行連接。對DubboInvoker包裝主要是AsyncToSyncInvoker

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
禁止轉載,如需轉載請通過簡信或評論聯(lián)系作者。

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