聊聊Dubbo - Dubbo可擴展機制實戰

摘要: 在Dubbo的官網上,Dubbo描述本身是一個高性能的RPC框架。今天我想聊聊Dubbo的另外一個很棒的特性, 就是它的可擴展性。java

1. Dubbo的擴展機制

在Dubbo的官網上,Dubbo描述本身是一個高性能的RPC框架。今天我想聊聊Dubbo的另外一個很棒的特性, 就是它的可擴展性。 如同羅馬不是一天建成的,任何系統都必定是從小系統不斷髮展成爲大系統的,想要從一開始就把系統設計的足夠完善是不可能的,相反的,咱們應該關注當下的需求,而後再不斷地對系統進行迭代。在代碼層面,要求咱們適當的對關注點進行抽象和隔離,在軟件不斷添加功能和特性時,依然能保持良好的結構和可維護性,同時容許第三方開發者對其功能進行擴展。在某些時候,軟件設計者對擴展性的追求甚至超過了性能。
在談到軟件設計時,可擴展性一直被談起,那到底什麼纔是可擴展性,什麼樣的框架纔算有良好的可擴展性呢?它必需要作到如下兩點:mysql

  1. 做爲框架的維護者,在添加一個新功能時,只須要添加一些新代碼,而不用大量的修改現有的代碼,即符合開閉原則。
  2. 做爲框架的使用者,在添加一個新功能時,不須要去修改框架的源碼,在本身的工程中添加代碼便可。
    Dubbo很好的作到了上面兩點。這要得益於Dubbo的微內核+插件的機制。接下來的章節中咱們會慢慢揭開Dubbo擴展機制的神祕面紗。

2. 可擴展的幾種解決方案

一般可擴展的實現有下面幾種:git

  • Factory模式
  • IoC容器
  • OSGI容器
    Dubbo做爲一個框架,不但願強依賴其餘的IoC容器,好比Spring,Guice。OSGI也是一個很重的實現,不適合Dubbo。最終Dubbo的實現參考了Java原生的SPI機制,但對其進行了一些擴展,以知足Dubbo的需求。

3. Java SPI機制

既然Dubbo的擴展機制是基於Java原生的SPI機制,那麼咱們就先來了解下Java SPI吧。瞭解了Java的SPI,也就是對Dubbo的擴展機制有一個基本的瞭解。若是對Java SPI比較瞭解的同窗,能夠跳過。
Java SPI(Service Provider Interface)是JDK內置的一種動態加載擴展點的實現。在ClassPath的META-INF/services目錄下放置一個與接口同名的文本文件,文件的內容爲接口的實現類,多個實現類用換行符分隔。JDK中使用java.util.ServiceLoader來加載具體的實現。 讓咱們經過一個簡單的例子,來看看Java SPI是如何工做的。github

  1. 定義一個接口IRepository用於實現數據儲存
  2. interface IRepository { void save(String data); }
  3. 提供IRepository的實現 IRepository有兩個實現。MysqlRepository和MongoRepository。
  4. class MysqlRepository implements IRepository { public void save(String data) { System.out.println("Save " + data + " to Mysql"); } }

public class MongoRepository implements IRepository { public void save(String data) { System.out.println("Save " + data + " to Mongo"); } }spring

  1. 添加配置文件 在META-INF/services目錄添加一個文件,文件名和接口全名稱相同,因此文件是META-INF/services/com.demo.IRepository。文件內容爲:
    com.demo.MongoRepository com.demo.MysqlRepository
  2. 經過ServiceLoader加載IRepository實現
    ServiceLoader serviceLoader = ServiceLoader.load(IRepository.class); Iterator it = serviceLoader.iterator(); while (it != null && it.hasNext()){ IRepository demoService = it.next(); System.out.println("class:" + demoService.getClass().getName()); demoService.save("tom"); }

在上面的例子中,咱們定義了一個擴展點和它的兩個實現。在ClassPath中添加了擴展的配置文件,最後使用ServiceLoader來加載全部的擴展點。sql

4. Dubbo的SPI機制

Java SPI的使用很簡單。也作到了基本的加載擴展點的功能。但Java SPI有如下的不足:負載均衡

  • 須要遍歷全部的實現,並實例化,而後咱們在循環中才能找到咱們須要的實現。
  • 配置文件中只是簡單的列出了全部的擴展實現,而沒有給他們命名。致使在程序中很難去準確的引用它們。
  • 擴展若是依賴其餘的擴展,作不到自動注入和裝配
  • 不提供相似於Spring的AOP功能
  • 擴展很難和其餘的框架集成,好比擴展裏面依賴了一個Spring bean,原生的Java SPI不支持
    因此Java SPI應付一些簡單的場景是能夠的,但對於Dubbo,它的功能仍是比較弱的。Dubbo對原生SPI機制進行了一些擴展。接下來,咱們就更深刻地瞭解下Dubbo的SPI機制。

5. Dubbo擴展點機制基本概念

在深刻學習Dubbo的擴展機制以前,咱們先明確Dubbo SPI中的一些基本概念。在接下來的內容中,咱們會屢次用到這些術語。框架

  1. 擴展點(Extension Point)
    是一個Java的接口。
  2. 擴展(Extension)
    擴展點的實現類。
  3. 擴展實例(Extension Instance)
    擴展點實現類的實例。
  4. 擴展自適應實例(Extension Adaptive Instance)
    第一次接觸這個概念時,可能不太好理解(我第一次也是這樣的...)。若是稱它爲擴展代理類,可能更好理解些。擴展的自適應實例其實就是一個Extension的代理,它實現了擴展點接口。在調用擴展點的接口方法時,會根據實際的參數來決定要使用哪一個擴展。好比一個IRepository的擴展點,有一個save方法。有兩個實現MysqlRepository和MongoRepository。IRepository的自適應實例在調用接口方法的時候,會根據save方法中的參數,來決定要調用哪一個IRepository的實現。若是方法參數中有repository=mysql,那麼就調用MysqlRepository的save方法。若是repository=mongo,就調用MongoRepository的save方法。和麪向對象的延遲綁定很相似。爲何Dubbo會引入擴展自適應實例的概念呢?dom

    • Dubbo中的配置有兩種,一種是固定的系統級別的配置,在Dubbo啓動以後就不會再改了。還有一種是運行時的配置,可能對於每一次的RPC,這些配置都不一樣。好比在xml文件中配置了超時時間是10秒鐘,這個配置在Dubbo啓動以後,就不會改變了。但針對某一次的RPC調用,能夠設置它的超時時間是30秒鐘,以覆蓋系統級別的配置。對於Dubbo而言,每一次的RPC調用的參數都是未知的。只有在運行時,根據這些參數才能作出正確的決定。
    • 不少時候,咱們的類都是一個單例的,好比Spring的bean,在Spring bean都實例化時,若是它依賴某個擴展點,可是在bean實例化時,是不知道究竟該使用哪一個具體的擴展實現的。這時候就須要一個代理模式了,它實現了擴展點接口,方法內部能夠根據運行時參數,動態的選擇合適的擴展實現。而這個代理就是自適應實例。 自適應擴展實例在Dubbo中的使用很是普遍,Dubbo中,每個擴展都會有一個自適應類,若是咱們沒有提供,Dubbo會使用字節碼工具爲咱們自動生成一個。因此咱們基本感受不到自適應類的存在。後面會有例子說明自適應類是怎麼工做的。
  5. @SPI
    @SPI註解做用於擴展點的接口上,代表該接口是一個擴展點。能夠被Dubbo的ExtentionLoader加載。若是沒有此ExtensionLoader調用會異常。
  6. @Adaptive
    @Adaptive註解用在擴展接口的方法上。表示該方法是一個自適應方法。Dubbo在爲擴展點生成自適應實例時,若是方法有@Adaptive註解,會爲該方法生成對應的代碼。方法內部會根據方法的參數,來決定使用哪一個擴展。
  7. ExtentionLoader
    相似於Java SPI的ServiceLoader,負責擴展的加載和生命週期維護。
  8. 擴展別名
    和Java SPI不一樣,Dubbo中的擴展都有一個別名,用於在應用中引用它們。好比

random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance 
其中的random,roundrobin就是對應擴展的別名。這樣咱們在配置文件中使用random或roundrobin就能夠了。ide

  1. 一些路徑
    和Java SPI從/META-INF/services目錄加載擴展配置相似,Dubbo也會從如下路徑去加載擴展配置文件:
  • META-INF/dubbo/internal
  • META-INF/dubbo
  • META-INF/services

6. Dubbo的LoadBalance擴展點解讀

在瞭解了Dubbo的一些基本概念後,讓咱們一塊兒來看一個Dubbo中實際的擴展點,對這些概念有一個更直觀的認識。
咱們選擇的是Dubbo中的LoadBalance擴展點。Dubbo中的一個服務,一般有多個Provider,consumer調用服務時,須要在多個Provider中選擇一個。這就是一個LoadBalance。咱們一塊兒來看看在Dubbo中,LoadBalance是如何成爲一個擴展點的。

  1. LoadBalance接口
    @SPI(RandomLoadBalance.NAME) public interface LoadBalance { @Adaptive("loadbalance") Invoker select(List> invokers, URL url, Invocation invocation) throws RpcException; }

LoadBalance接口只有一個select方法。select方法從多個invoker中選擇其中一個。上面代碼中和Dubbo SPI相關的元素有:

  • @SPI(RandomLoadBalance.NAME) @SPI做用於LoadBalance接口,表示接口LoadBalance是一個擴展點。若是沒有@SPI註解,試圖去加載擴展時,會拋出異常。@SPI註解有一個參數,該參數表示該擴展點的默認實現的別名。若是沒有顯示的指定擴展,就使用默認實現。RandomLoadBalance.NAME是一個常量,值是"random",是一個隨機負載均衡的實現。 random的定義在配置文件META-INF/dubbo/internal/com.alibaba.dubbo.rpc.cluster.LoadBalance中:
    random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance leastactive=com.alibaba.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance consistenthash=com.alibaba.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance

能夠看到文件中定義了4個LoadBalance的擴展實現。因爲負載均衡的實現不是本次的內容,這裏就不過多說明。只用知道Dubbo提供了4種負載均衡的實現,咱們能夠經過xml文件,properties文件,JVM參數顯式的指定一個實現。若是沒有,默認使用隨機。

  • @Adaptive("loadbalance") @Adaptive註解修飾select方法,代表方法select方法是一個可自適應的方法。Dubbo會自動生成該方法對應的代碼。當調用select方法時,會根據具體的方法參數來決定調用哪一個擴展實現的select方法。@Adaptive註解的參數loadbalance表示方法參數中的loadbalance的值做爲實際要調用的擴展實例。 但奇怪的是,咱們發現select的方法中並無loadbalance參數,那怎麼獲取loadbalance的值呢?select方法中還有一個URL類型的參數,Dubbo就是從URL中獲取loadbalance的值的。這裏涉及到Dubbo的URL總線模式,簡單說,URL中包含了RPC調用中的全部參數。URL類中有一個Map parameters字段,parameters中就包含了loadbalance。
  1. 獲取LoadBalance擴展
    Dubbo中獲取LoadBalance的代碼以下:

LoadBalance lb = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName); 
使用ExtensionLoader.getExtensionLoader(LoadBalance.class)方法獲取一個ExtensionLoader的實例,而後調用getExtension,傳入一個擴展的別名來獲取對應的擴展實例。

7. 自定義一個LoadBalance擴展

本節中,咱們經過一個簡單的例子,來本身實現一個LoadBalance,並把它集成到Dubbo中。我會列出一些關鍵的步驟和代碼,也能夠從這個地址(https://github.com/vangoleo/dubbo-spi-demo)下載完整的demo。

  1. 實現LoadBalance接口
    首先,編寫一個本身實現的LoadBalance,由於是爲了演示Dubbo的擴展機制,而不是LoadBalance的實現,因此這裏LoadBalance的實現很是簡單,選擇第一個invoker,並在控制檯輸出一條日誌。

package com.dubbo.spi.demo.consumer; public class DemoLoadBalance implements LoadBalance { @Override public Invoker select(List> invokers, URL url, Invocation invocation) throws RpcException { System.out.println("DemoLoadBalance: Select the first invoker..."); return invokers.get(0); } }

  1. 添加擴展配置文件
    添加文件:META-INF/dubbo/com.alibaba.dubbo.rpc.cluster.LoadBalance。文件內容以下:

demo=com.dubbo.spi.demo.consumer.DemoLoadBalance

  1. 配置使用自定義LoadBalance
    經過上面的兩步,已經添加了一個名字爲demo的LoadBalance實現,並在配置文件中進行了相應的配置。接下來,須要顯式的告訴Dubbo使用demo的負載均衡實現。若是是經過spring的方式使用Dubbo,能夠在xml文件中進行設置。


在consumer端的dubbo:reference中配置

  1. 啓動Dubbo
    啓動Dubbo,調用一次IHelloService,能夠看到控制檯會輸出一條DemoLoadBalance: Select the first invoker...日誌。說明Dubbo的確是使用了咱們自定義的LoadBalance。

總結

到此,咱們從Java SPI開始,瞭解了Dubbo SPI 的基本概念,並結合了Dubbo中的LoadBalance加深了理解。最後,咱們還實踐了一下,建立了一個自定義LoadBalance,並集成到Dubbo中。相信經過這裏理論和實踐的結合,你們對Dubbo的可擴展有更深刻的理解。

總結一下,Dubbo SPI有如下的特色:

• 對Dubbo進行擴展,不須要改動Dubbo的源碼
• 自定義的Dubbo的擴展點實現,是一個普通的Java類,Dubbo沒有引入任何Dubbo特有的元素,對代碼侵入性幾乎爲零。
• 將擴展註冊到Dubbo中,只須要在ClassPath中添加配置文件。使用簡單。並且不會對現有代碼形成影響。符合開閉原則。
• Dubbo的擴展機制支持IoC,AoP等高級功能
• Dubbo的擴展機制能很好的支持第三方IoC容器,默認支持Spring Bean,可本身擴展來支持其餘容器,好比Google的Guice。
• 切換擴展點的實現,只須要在配置文件中修改具體的實現,不須要改代碼。使用方便。

原文連接

本文爲雲棲社區原創內容,未經容許不得轉載。

相關文章
相關標籤/搜索