在Dubbo的官網上,Dubbo描述本身是一個高性能的RPC框架。今天我想聊聊Dubbo的另外一個很棒的特性, 就是它的可擴展性。 如同羅馬不是一天建成的,任何系統都必定是從小系統不斷髮展成爲大系統的,想要從一開始就把系統設計的足夠完善是不可能的,相反的,咱們應該關注當下的需求,而後再不斷地對系統進行迭代。在代碼層面,要求咱們適當的對關注點進行抽象和隔離,在軟件不斷添加功能和特性時,依然能保持良好的結構和可維護性,同時容許第三方開發者對其功能進行擴展。在某些時候,軟件設計者對擴展性的追求甚至超過了性能。java
在談到軟件設計時,可擴展性一直被談起,那到底什麼纔是可擴展性,什麼樣的框架纔算有良好的可擴展性呢?它必需要作到如下兩點:mysql
一般可擴展的實現有下面幾種:sql
既然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是如何工做的。bash
1.定義一個接口IRepository用於實現數據儲存負載均衡
public interface IRepository { void save(String data); }框架
2.提供IRepository的實現 IRepository有兩個實現。MysqlRepository和MongoRepository。dom
public class MysqlRepository implements IRepository { public void save(String data) { System.out.println("Save " + data + " to Mysql"); } }ide
public class MongoRepository implements IRepository { public void save(String data) { System.out.println("Save " + data + " to Mongo"); } }工具
3.添加配置文件 在META-INF/services
目錄添加一個文件,文件名和接口全名稱相同,因此文件是META-INF/services/com.demo.IRepository
。文件內容爲:
com.demo.MongoRepository com.demo.MysqlRepository
4.經過ServiceLoader加載IRepository實現
ServiceLoader<IRepository> serviceLoader = ServiceLoader.load(IRepository.class);
Iterator<IRepository> it = serviceLoader.iterator();
while (it != null && it.hasNext()){
IRepository demoService = it.next();
System.out.println("class:" + demoService.getClass().getName());
demoService.save("tom");
}
複製代碼
在上面的例子中,咱們定義了一個擴展點和它的兩個實現。在ClassPath中添加了擴展的配置文件,最後使用ServiceLoader來加載全部的擴展點。 最終的輸出結果爲:
class:testDubbo.MongoRepository Save tom to Mongo
class:testDubbo.MysqlRepository Save tom to Mysql
複製代碼
Java SPI的使用很簡單。也作到了基本的加載擴展點的功能。但Java SPI有如下的不足:
因此Java SPI應付一些簡單的場景是能夠的,但對於Dubbo,它的功能仍是比較弱的。Dubbo對原生SPI機制進行了一些擴展。接下來,咱們就更深刻地瞭解下Dubbo的SPI機制。
在深刻學習Dubbo的擴展機制以前,咱們先明確Dubbo SPI中的一些基本概念。在接下來的內容中,咱們會屢次用到這些術語。
5.1 擴展點(Extension Point)
是一個Java的接口。
5.2 擴展(Extension)
擴展點的實現類。
5.3 擴展實例(Extension Instance)
擴展點實現類的實例。
5.4 擴展自適應實例(Extension Adaptive Instance)
第一次接觸這個概念時,可能不太好理解(我第一次也是這樣的...)。若是稱它爲擴展代理類,可能更好理解些。擴展的自適應實例其實就是一個Extension的代理,它實現了擴展點接口。在調用擴展點的接口方法時,會根據實際的參數來決定要使用哪一個擴展。好比一個IRepository的擴展點,有一個save方法。有兩個實現MysqlRepository和MongoRepository。IRepository的自適應實例在調用接口方法的時候,會根據save方法中的參數,來決定要調用哪一個IRepository的實現。若是方法參數中有repository=mysql,那麼就調用MysqlRepository的save方法。若是repository=mongo,就調用MongoRepository的save方法。和麪向對象的延遲綁定很相似。爲何Dubbo會引入擴展自適應實例的概念呢?
5.5 @SPI
@SPI
註解做用於擴展點的接口上,代表該接口是一個擴展點。能夠被Dubbo的ExtentionLoader
加載。若是沒有此ExtensionLoader調用會異常。
5.6 @Adaptive
@Adaptive
註解用在擴展接口的方法上。表示該方法是一個自適應方法。Dubbo在爲擴展點生成自適應實例時,若是方法有@Adaptive註解,會爲該方法生成對應的代碼。方法內部會根據方法的參數,來決定使用哪一個擴展。 @Adaptive註解用在類上表明實現一個裝飾類,相似於設計模式中的裝飾模式,它主要做用是返回指定類,目前在整個系統中AdaptiveCompiler、AdaptiveExtensionFactory這兩個類擁有該註解。
5.7 ExtentionLoader
相似於Java SPI的ServiceLoader,負責擴展的加載和生命週期維護。
5.8 擴展別名
和Java SPI不一樣,Dubbo中的擴展都有一個別名,用於在應用中引用它們。好比
random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
複製代碼
5.9 一些路徑
和Java SPI從/META-INF/services目錄加載擴展配置相似,Dubbo也會從如下路徑去加載擴展配置文件:
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
在瞭解了Dubbo的一些基本概念後,讓咱們一塊兒來看一個Dubbo中實際的擴展點,對這些概念有一個更直觀的認識。
咱們選擇的是Dubbo中的LoadBalance擴展點。Dubbo中的一個服務,一般有多個Provider,consumer調用服務時,須要在多個Provider中選擇一個。這就是一個LoadBalance。咱們一塊兒來看看在Dubbo中,LoadBalance是如何成爲一個擴展點的。
6.1 LoadBalance接口
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> 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參數顯式的指定一個實現。若是沒有,默認使用隨機。