Dubbo服務釋出的幾點心得
Dubbo服務釋出(服務暴露)是Dubbo框架啟動過程中服務初始化、啟動本地監聽、註冊服務資訊的
過程,是Dubbo對外實現可用性的基礎!
本篇主要本著涵蓋全面、核心突出的原則去分析一下Dubbo的服務釋出過程,以使自己對框架的
理解更為透徹! 分析的幾個維度如下:
1)Dubbo服務的xml配置是如何解析的
2)釋出流程是何時觸發的
3)統一資料模型URL是如何發揮作用的
4)攔截器
5)服務的本地暴露(服務監聽初始化)和服務資訊的註冊
6)Dubbo服務暴露的全流程對Dubbo應用場景的啟示
進入正題,
一、Dubbo服務的xml配置是如何解析的
Dubbo框架利用spring對標籤拓展的支援,定義了一些列自定義標籤,以<dubbo:xx>開頭, 然後
定義自己的標籤處理器和對應的標籤類,先簡單講一下spring標籤拓展的規則:
(a) 定義自定義標籤的名稱空間處理器, 例如在Dubbo就是 DubboNamespaceHandler
(b) 定以特定標籤的解析器類和標籤屬性描述檔案*.xsd ,如Dubbo中的<dubbo:service> 中的解析器DubboBeanDefinitionParser 和 dubbo.xsd
(c) 定義名稱空間與處理器關聯檔案 spring.handlers (
(d) 定義名稱空間的*.xsd檔案的引用路徑檔案 spring.schemas (同上)
通過自定義自己的一套標籤,dubbo將服務類納入spring容器的管理中,具體的解析可以看原始碼,如下:
(1) 處理器定義
(2) 解析器分析 DubboBeanDefinitionParser
通過以上分析基本上了解了dubbo如何將自己需要關注的xml配置bean納入到spring管理中去了
二、Dubbo的服務釋出如何觸發的
當<dubbo:service>被spring解析和加入容器的時候,Dubbo框架對應的標籤類實現了一系列
的spring生命週期事件介面,從而參與到spring容器建立過程中去,看原始碼如下:
dubbo在spring載入重新整理bean的時候,會監聽該事件通知,從而觸發整個釋出流程,原始碼如圖:
小拓展 : 我們知道Dubbo服務釋出的時候支援延時暴露,譬如這樣配置 <dubbo:service ... delay="10" >,Dubbo框架是如何實現的,
其實沒有什麼高超技巧,開啟一個延時執行緒,如圖
三、統一資料模型URL
URL是Dubbo自定義的引數傳遞的模型類,與Dubbo的拓展載入機制相配合,實現了類之間的無縫適配,
引數資訊集中管理,是Dubbo各層之間的資料傳遞封裝, 簡單舉幾個例子,以便於理解!
loadRegistries(boolean provider) 方法負責抽離各個Dubbo的配置元件的資訊、版本號、時間戳、執行緒id等, 然後組
裝成URL,一個以zookeeper為註冊中心的url例項如下:
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&organization=dubbox&owner=programmer&pid=3772®istry=zookeeper×tamp=1493689627979
這裡還有一非常重要的欄位沒有列舉出來 就是protocol,為甚說他重要呢,看幾個例項 :
在ServiceConfig.java類中有一本地方法 doExportUrlsFor1Protocol,有一段程式碼負責本地代理構建和服務釋出,
(1) 如下,是代理工廠適配類的結構
com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adpative implements com.alibaba.dubbo.rpc.ProxyFactory {
public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws java.lang.Object {
if (arg2 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg2;
String extName = url.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getInvoker(arg0, arg1, arg2);
}
public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("proxy", "javassist");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
return extension.getProxy(arg0);
}
}
哈哈是不是有種恍然醒悟的感覺,沒錯,類似這種適配,就是通過統一資料模型url中的特定業務標誌引數適配的
(2)如下,協議適配類結構這樣的
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
public void destroy() {throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
}
通過以上的分析我們可以看出Dubbo的URL實際上為整個框架提供了統一的資料模型、資訊集中管理、類自動適配。
這裡有個非常重要的概念要引入一下,Invoker(代理執行器,這個是我杜撰的,至於為什麼這樣叫,待會講 )
Invoker在Dubbo中實際上是一個抽象介面,許多協議實現了該介面,諸如DubboInvoker 等,單就服務釋出而言,
該物件建立過程是這樣的,本地服務類->裝飾類->建立Invoker的抽象例項AbstractProxyInvoker
原始碼這樣的,
本地服務類的包裝類Wrapper0,其結構如下(僅是部分程式碼):
publicclassWrapper0extendsWrapperimplementsDC{
public Object invokeMethod(Object o, String n, Class[] p, Object[] v) throws java.lang.reflect.InvocationTargetException{
com.alibaba.dubbo.demo.bid.BidService w;
try{
w = ((com.alibaba.dubbo.demo.bid.BidService)$1);
}catch(Throwable e){
throw new IllegalArgumentException(e);
}
try{
if( "bid".equals( $2 ) && $3.length == 1 ){
return ($w)w.bid((com.alibaba.dubbo.demo.bid.BidRequest)$4[0]);
}
if( "throwNPE".equals( $2 ) && $3.length == 0 ) {
w.throwNPE();
return null;
}
} catch(Throwable e) {
throw new java.lang.reflect.InvocationTargetException(e);
}
throw new com.alibaba.dubbo.common.bytecode.NoSuchMethodException("Not found method \""+$2+"\" in class com.alibaba.dubbo.demo.bid.BidService.");
}
}
顯然,裝飾類的invokeMethod方法包裝了對本地實際服務類的方法的呼叫,結合上面了分析,Invoker實際上代理了對本服務的
執行,因此稱之為代理執行器
四、攔截器
Dubbo的攔截器服務是服務呼叫時,執行Invoker過程中的功能增強,其實時序圖如下:
有沒有眼前一亮,這不就是spring中的aop攔截器鏈嘛,沒錯原理是一樣的,所以技術思想可以超越具體的架構,而去解決通用場景的問題
Dubbo在執行export過程中,會去構建關聯Invoker的呼叫Filter鏈,我們回到原始碼中繼續分析,如圖
其中的protocol是 Protocol$Adpative型別的, 通過Invoker攜帶的URL物件實現了方法的適配,最終實際上執行的是RegistryProtocol.export方法(因為
url中的protocol="registry"), OK,接下來會依次執行該類的裝飾類ProtocolFilterWrapper、ProtocolListenerWrapper類中的export方法,如圖所示
有點小複雜,不過我可以簡單解釋一下,上節談到Dubbo的拓展點載入機制會用特定介面型別的裝飾類對應的類進行裝飾,譬如上述所講的Protocol型別的
實現類, 當執行完上述的RegistryProtocol的裝飾類方法後, 會進入RegistryProtocol類中export執行,如圖
在doLocalExport 方法中執行 protocol.export()時, (該protocol根據前述的適配規則,是DubboProtocol型別), 所以又順次進入了裝飾類ProtocolFilterWrapper、ProtocolListenerWrapper類中的export方法中, 接下來看看ProtocolFilterWrapper裝飾類具體實現細節:
經過漫長的前夜,終於迎來了攔截器鏈的黎明瞭,分析 buildInvokerChain 方法
說的直接點就是每一個Invoker都包含了對呼叫鏈上緊鄰的下一個Invoker的引用, 在Invoker每一個呼叫方法invoke中都對應一個特定的攔截器
,它觸發對下一個Invoker的呼叫,於是,整個攔截器鏈順次執行,直到代理執行器Invoker執行完畢返回!
五、服務的本地暴露和服務資訊註冊
服務的暴露過程實際上就是建立Exporter物件、將服務相關資訊註冊到註冊中心對外發布的過程, 這裡用時序圖
來表示一下
本地服務監聽的初始化我會在Dubbo的RPC通訊分析時單獨去講解,此處不再展開,重點講一下服務資訊註冊
Dubbo抽象了註冊中心服務的通用介面 Registry, 定義了基本的操作方法, 而通過RegistryFactory適配類和url獲取
不同的註冊中心例項。
六、Dubbo服務暴露的全流程對Dubbo應用場景的啟示
(1) 定義服務介面的粒度不要太細,控制介面的數量(dubbo是按服務介面釋出的,每次都會消耗系統資源);
(2) 控制服務介面方法的數量(否則代理類Wrapper中的invokeMethod會急劇膨脹);
好了,就囉嗦到這吧!
總重每一個堅持改變,讓現狀變得更好的人!