1. 程式人生 > >Dubbo服務釋出的幾點心得

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 (

這個是spring約定的檔名,不能改,spring在啟動時會自動載入該檔案)

      (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&registry=zookeeper&timestamp=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會急劇膨脹);

好了,就囉嗦到這吧!

總重每一個堅持改變,讓現狀變得更好的人!