在使用 dubbo 的時候,我們對于遠(yuǎn)程服務(wù)調(diào)用是無感知的。當(dāng)需要調(diào)用遠(yuǎn)程服務(wù)的時候我們只需要進(jìn)行以下配置,就可以像本地調(diào)用的方式調(diào)用遠(yuǎn)程服務(wù):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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/dubbo.xsd">
<dubbo:application name="demo-consumer"/>
<dubbo:registry address="zookeeper://localhost:2181"/>
<dubbo:reference id="demoService" interface="com.alibaba.dubbo.demo.DemoService"/>
</beans>
下面的圖片是 dubbo 官方服務(wù)消費(fèi)者消費(fèi)一個服務(wù)的詳細(xì)過程的圖:
dubbo 的服務(wù)引用其實(shí)就是把上面的配置解析成 dubbo 框架的 ReferenceConfig(接口引用配置類),然后調(diào)用ReferenceConfig類的 init 方法調(diào)用 Protocol 的 refer方法生成 Invoker 實(shí)例(如上圖中的紅色部分),這是服務(wù)消費(fèi)的關(guān)鍵。接下來把Invoker轉(zhuǎn)換為客戶端需要的接口(如:HelloService)。
關(guān)于每種協(xié)議如 RMI/Dubbo/Web service 等它們在調(diào)用 refer 方法生成 Invoker 實(shí)例。最后調(diào)用 ProxyFactory#getProxy(Invoker)生成遠(yuǎn)程暴露服務(wù)接口的代理對象。
那么 dubbo 在框架內(nèi)部是如何實(shí)現(xiàn)的呢?下面我們來具體分析一下它的源碼實(shí)現(xiàn)。
我們在 xml 里面配置 dubbo 服務(wù)的引用,其實(shí)是 dubbo 實(shí)現(xiàn)了自定義命名空間的方式來集成 Spring 框架。這樣通過 Spring 的 IOC 特性可以很方便的創(chuàng)建 dubbo 的配置對象。
對于 <reference: /> 標(biāo)簽 Spring 會把它解析成 ReferenceBean 對象,并且這個對象是一個 FactoryBean。所以當(dāng)我們通過 Spring 依賴注入這個對象的時候會調(diào)用 ReferenceBean#getObject 獲取接口對象的實(shí)例。它會把參數(shù)解析到一個 Map 中,然后根據(jù)其中的參數(shù)創(chuàng)建服務(wù)引用的代理對象。Map 里面的值大概如下所示:
1、ReferenceConfig#createProxy
1、loadRegistries()加載注冊中心配置,因?yàn)?dubbo 支持多配置中心,所以返回 URL 的集合。
2、便利注冊中心 List<URL>集合:加載監(jiān)控中心 URL,如果配置了監(jiān)控中心就在注冊 URL 加上monitor;把服務(wù)引用的配置參數(shù)添加到注冊 URL 的 refer參數(shù)上。
3、Protocol#refer引用遠(yuǎn)程服務(wù),通過注冊中心 URL 與 接口 Class 創(chuàng)建 Invoker 調(diào)用對象。
4、proxyFactory.getProxy(invoker);通過代理工廠創(chuàng)建遠(yuǎn)程服務(wù)代理返回給使用者。
2、Procotol#refer
和服務(wù)暴露一樣,consumer 端進(jìn)行服務(wù)調(diào)用的時候,可以對 dubbo 框架進(jìn)行擴(kuò)展:com.alibaba.dubbo.rpc.ExporterListener與com.alibaba.dubbo.rpc.Filter。所以獲取到的 Procotol 實(shí)例的結(jié)構(gòu)是:
- ProtocolListenerWrapper
- ProtocolFilterWrapper
- RegistryProtocol
- ProtocolFilterWrapper
1、RegistryFactory#getRegistry獲取到 zookeeper 注冊中心,和服務(wù)暴露獲取注冊中心的邏輯一樣。
2、創(chuàng)建注冊服務(wù)目錄 RegistryDirectory
3、注冊服務(wù)消費(fèi)者 URL 到 zookeeper,其實(shí)就是創(chuàng)建 zookeeper 的節(jié)點(diǎn),和服務(wù)端發(fā)布類似。
/dubbo/com.alibaba.dubbo.demo.DemoService/consumers
/consumer%3A%2F%2F192.168.20.1%2Fcom.alibaba.dubbo.demo.DemoService%3Fapplication%3Ddemo-consumer%26category%3Dconsumers%26check%3Dfalse%26dubbo%3D2.0.0%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D3808%26qos.port%3D33333%26side%3Dconsumer%26timestamp%3D1522590290256
4、訂閱 zookeeper 以下結(jié)點(diǎn),當(dāng)服務(wù)發(fā)生變更時,銷毀無效的 Invoke.刷新 RegistryDirectory 中的Map<String, List<Invoker<T>>> methodInvokerMap對象。
- /dubbo/com.alibaba.dubbo.demo.DemoService/providers
- /dubbo/com.alibaba.dubbo.demo.DemoService/configurators
- /dubbo/com.alibaba.dubbo.demo.DemoService/routers
5、調(diào)用cluster#join(directory) 合并 invoker 創(chuàng)建并提供集群 failover (故障轉(zhuǎn)移)調(diào)用策略
- cluster#join(directory)//加入集群路由
- ExtensionLoader#getExtensionLoader(Cluster.class).getExtension("failover");
- MockClusterWrapper#join
- this.cluster#join(directory)
- FailoverCluster#join
- return new FailoverClusterInvoker<T>(directory)
- FailoverCluster#join
- this.cluster#join(directory)
- MockClusterWrapper#join
- ExtensionLoader#getExtensionLoader(Cluster.class).getExtension("failover");
3、DubboProtocol#refer
1、通過 接口Class對象、服務(wù)URL、ExchangeClient和Set<Invoker<?>> invokers創(chuàng)建 DubboInvoke 對象
2、獲取 ExchangeClient 數(shù)據(jù)交換客戶端 HeaderExchangeClient,并創(chuàng)建心跳連接。默認(rèn)是創(chuàng)建 Netty 客戶端用來調(diào)用暴露的遠(yuǎn)程調(diào)用。
3、將創(chuàng)建的 invoker (服務(wù)調(diào)用者)返回給目錄服務(wù),用來刷新 RegistryDirectory 中的Map<String, List<Invoker<T>>> methodInvokerMap對象。
4、ProxyFactory#getProxy
通過代理工廠創(chuàng)建服務(wù)引用接口的代理對象,用于訪問暴露的遠(yuǎn)程服務(wù)。
1、根據(jù) dubbo SPI 機(jī)制默認(rèn)獲取到 JavassistProxyFactory 對象
2、通過上面獲取到的 Invoke對象以及引用的遠(yuǎn)程服務(wù)接口 + dubbo 里面的 EchoService 調(diào)用AbstractProxyFactory#getProxy(Invoker<T>, Class<?>[])獲取到代理對象。
3、使用 JDK 里面的 InvocationHandler 對象代理遠(yuǎn)程暴露服務(wù) Invoke 調(diào)用對象創(chuàng)建遠(yuǎn)程暴露服務(wù)接口代理對象。