摘要:今天我想聊聊的另一個(gè)很棒的特性就是它的可擴(kuò)展性。的擴(kuò)展機(jī)制在的官網(wǎng)上,描述自己是一個(gè)高性能的框架。接下來的章節(jié)中我們會(huì)慢慢揭開擴(kuò)展機(jī)制的神秘面紗。擴(kuò)展擴(kuò)展點(diǎn)的實(shí)現(xiàn)類。的定義在配置文件中可以看到文件中定義了個(gè)的擴(kuò)展實(shí)現(xiàn)。
摘要: 在Dubbo的官網(wǎng)上,Dubbo描述自己是一個(gè)高性能的RPC框架。今天我想聊聊Dubbo的另一個(gè)很棒的特性, 就是它的可擴(kuò)展性。
Dubbo的擴(kuò)展機(jī)制
在Dubbo的官網(wǎng)上,Dubbo描述自己是一個(gè)高性能的RPC框架。今天我想聊聊Dubbo的另一個(gè)很棒的特性, 就是它的可擴(kuò)展性。 如同羅馬不是一天建成的,任何系統(tǒng)都一定是從小系統(tǒng)不斷發(fā)展成為大系統(tǒng)的,想要從一開始就把系統(tǒng)設(shè)計(jì)的足夠完善是不可能的,相反的,我們應(yīng)該關(guān)注當(dāng)下的需求,然后再不斷地對(duì)系統(tǒng)進(jìn)行迭代。在代碼層面,要求我們適當(dāng)?shù)膶?duì)關(guān)注點(diǎn)進(jìn)行抽象和隔離,在軟件不斷添加功能和特性時(shí),依然能保持良好的結(jié)構(gòu)和可維護(hù)性,同時(shí)允許第三方開發(fā)者對(duì)其功能進(jìn)行擴(kuò)展。在某些時(shí)候,軟件設(shè)計(jì)者對(duì)擴(kuò)展性的追求甚至超過了性能。
在談到軟件設(shè)計(jì)時(shí),可擴(kuò)展性一直被談起,那到底什么才是可擴(kuò)展性,什么樣的框架才算有良好的可擴(kuò)展性呢?它必須要做到以下兩點(diǎn):
作為框架的維護(hù)者,在添加一個(gè)新功能時(shí),只需要添加一些新代碼,而不用大量的修改現(xiàn)有的代碼,即符合開閉原則。
作為框架的使用者,在添加一個(gè)新功能時(shí),不需要去修改框架的源碼,在自己的工程中添加代碼即可。
Dubbo很好的做到了上面兩點(diǎn)。這要得益于Dubbo的微內(nèi)核+插件的機(jī)制。接下來的章節(jié)中我們會(huì)慢慢揭開Dubbo擴(kuò)展機(jī)制的神秘面紗。
可擴(kuò)展的幾種解決方案
通??蓴U(kuò)展的實(shí)現(xiàn)有下面幾種:
Factory模式
IoC容器
OSGI容器
Dubbo作為一個(gè)框架,不希望強(qiáng)依賴其他的IoC容器,比如Spring,Guice。OSGI也是一個(gè)很重的實(shí)現(xiàn),不適合Dubbo。最終Dubbo的實(shí)現(xiàn)參考了Java原生的SPI機(jī)制,但對(duì)其進(jìn)行了一些擴(kuò)展,以滿足Dubbo的需求。
Java SPI機(jī)制
既然Dubbo的擴(kuò)展機(jī)制是基于Java原生的SPI機(jī)制,那么我們就先來了解下Java SPI吧。了解了Java的SPI,也就是對(duì)Dubbo的擴(kuò)展機(jī)制有一個(gè)基本的了解。如果對(duì)Java SPI比較了解的同學(xué),可以跳過。
Java SPI(Service Provider Interface)是JDK內(nèi)置的一種動(dòng)態(tài)加載擴(kuò)展點(diǎn)的實(shí)現(xiàn)。在ClassPath的META-INF/services目錄下放置一個(gè)與接口同名的文本文件,文件的內(nèi)容為接口的實(shí)現(xiàn)類,多個(gè)實(shí)現(xiàn)類用換行符分隔。JDK中使用java.util.ServiceLoader來加載具體的實(shí)現(xiàn)。 讓我們通過一個(gè)簡(jiǎn)單的例子,來看看Java SPI是如何工作的。
定義一個(gè)接口IRepository用于實(shí)現(xiàn)數(shù)據(jù)儲(chǔ)存
interface IRepository { void save(String data); }
提供IRepository的實(shí)現(xiàn) IRepository有兩個(gè)實(shí)現(xiàn)。MysqlRepository和MongoRepository。
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"); } }
添加配置文件 在META-INF/services目錄添加一個(gè)文件,文件名和接口全名稱相同,所以文件是META-INF/services/com.demo.IRepository。文件內(nèi)容為:
com.demo.MongoRepository com.demo.MysqlRepository
通過ServiceLoader加載IRepository實(shí)現(xiàn)
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"); }
在上面的例子中,我們定義了一個(gè)擴(kuò)展點(diǎn)和它的兩個(gè)實(shí)現(xiàn)。在ClassPath中添加了擴(kuò)展的配置文件,最后使用ServiceLoader來加載所有的擴(kuò)展點(diǎn)。
Dubbo的SPI機(jī)制
Java SPI的使用很簡(jiǎn)單。也做到了基本的加載擴(kuò)展點(diǎn)的功能。但Java SPI有以下的不足:
需要遍歷所有的實(shí)現(xiàn),并實(shí)例化,然后我們?cè)谘h(huán)中才能找到我們需要的實(shí)現(xiàn)。
配置文件中只是簡(jiǎn)單的列出了所有的擴(kuò)展實(shí)現(xiàn),而沒有給他們命名。導(dǎo)致在程序中很難去準(zhǔn)確的引用它們。
擴(kuò)展如果依賴其他的擴(kuò)展,做不到自動(dòng)注入和裝配
不提供類似于Spring的AOP功能
擴(kuò)展很難和其他的框架集成,比如擴(kuò)展里面依賴了一個(gè)Spring bean,原生的Java SPI不支持
所以Java SPI應(yīng)付一些簡(jiǎn)單的場(chǎng)景是可以的,但對(duì)于Dubbo,它的功能還是比較弱的。Dubbo對(duì)原生SPI機(jī)制進(jìn)行了一些擴(kuò)展。接下來,我們就更深入地了解下Dubbo的SPI機(jī)制。
Dubbo擴(kuò)展點(diǎn)機(jī)制基本概念
在深入學(xué)習(xí)Dubbo的擴(kuò)展機(jī)制之前,我們先明確Dubbo SPI中的一些基本概念。在接下來的內(nèi)容中,我們會(huì)多次用到這些術(shù)語(yǔ)。
擴(kuò)展點(diǎn)(Extension Point)
是一個(gè)Java的接口。
擴(kuò)展(Extension)
擴(kuò)展點(diǎn)的實(shí)現(xiàn)類。
擴(kuò)展實(shí)例(Extension Instance)
擴(kuò)展點(diǎn)實(shí)現(xiàn)類的實(shí)例。
擴(kuò)展自適應(yīng)實(shí)例(Extension Adaptive Instance)
第一次接觸這個(gè)概念時(shí),可能不太好理解(我第一次也是這樣的...)。如果稱它為擴(kuò)展代理類,可能更好理解些。擴(kuò)展的自適應(yīng)實(shí)例其實(shí)就是一個(gè)Extension的代理,它實(shí)現(xiàn)了擴(kuò)展點(diǎn)接口。在調(diào)用擴(kuò)展點(diǎn)的接口方法時(shí),會(huì)根據(jù)實(shí)際的參數(shù)來決定要使用哪個(gè)擴(kuò)展。比如一個(gè)IRepository的擴(kuò)展點(diǎn),有一個(gè)save方法。有兩個(gè)實(shí)現(xiàn)MysqlRepository和MongoRepository。IRepository的自適應(yīng)實(shí)例在調(diào)用接口方法的時(shí)候,會(huì)根據(jù)save方法中的參數(shù),來決定要調(diào)用哪個(gè)IRepository的實(shí)現(xiàn)。如果方法參數(shù)中有repository=mysql,那么就調(diào)用MysqlRepository的save方法。如果repository=mongo,就調(diào)用MongoRepository的save方法。和面向?qū)ο蟮难舆t綁定很類似。為什么Dubbo會(huì)引入擴(kuò)展自適應(yīng)實(shí)例的概念呢?
Dubbo中的配置有兩種,一種是固定的系統(tǒng)級(jí)別的配置,在Dubbo啟動(dòng)之后就不會(huì)再改了。還有一種是運(yùn)行時(shí)的配置,可能對(duì)于每一次的RPC,這些配置都不同。比如在xml文件中配置了超時(shí)時(shí)間是10秒鐘,這個(gè)配置在Dubbo啟動(dòng)之后,就不會(huì)改變了。但針對(duì)某一次的RPC調(diào)用,可以設(shè)置它的超時(shí)時(shí)間是30秒鐘,以覆蓋系統(tǒng)級(jí)別的配置。對(duì)于Dubbo而言,每一次的RPC調(diào)用的參數(shù)都是未知的。只有在運(yùn)行時(shí),根據(jù)這些參數(shù)才能做出正確的決定。
很多時(shí)候,我們的類都是一個(gè)單例的,比如Spring的bean,在Spring bean都實(shí)例化時(shí),如果它依賴某個(gè)擴(kuò)展點(diǎn),但是在bean實(shí)例化時(shí),是不知道究竟該使用哪個(gè)具體的擴(kuò)展實(shí)現(xiàn)的。這時(shí)候就需要一個(gè)代理模式了,它實(shí)現(xiàn)了擴(kuò)展點(diǎn)接口,方法內(nèi)部可以根據(jù)運(yùn)行時(shí)參數(shù),動(dòng)態(tài)的選擇合適的擴(kuò)展實(shí)現(xiàn)。而這個(gè)代理就是自適應(yīng)實(shí)例。 自適應(yīng)擴(kuò)展實(shí)例在Dubbo中的使用非常廣泛,Dubbo中,每一個(gè)擴(kuò)展都會(huì)有一個(gè)自適應(yīng)類,如果我們沒有提供,Dubbo會(huì)使用字節(jié)碼工具為我們自動(dòng)生成一個(gè)。所以我們基本感覺不到自適應(yīng)類的存在。后面會(huì)有例子說明自適應(yīng)類是怎么工作的。
@SPI
@SPI注解作用于擴(kuò)展點(diǎn)的接口上,表明該接口是一個(gè)擴(kuò)展點(diǎn)。可以被Dubbo的ExtentionLoader加載。如果沒有此ExtensionLoader調(diào)用會(huì)異常。
@Adaptive
@Adaptive注解用在擴(kuò)展接口的方法上。表示該方法是一個(gè)自適應(yīng)方法。Dubbo在為擴(kuò)展點(diǎn)生成自適應(yīng)實(shí)例時(shí),如果方法有@Adaptive注解,會(huì)為該方法生成對(duì)應(yīng)的代碼。方法內(nèi)部會(huì)根據(jù)方法的參數(shù),來決定使用哪個(gè)擴(kuò)展。
ExtentionLoader
類似于Java SPI的ServiceLoader,負(fù)責(zé)擴(kuò)展的加載和生命周期維護(hù)。
擴(kuò)展別名
和Java SPI不同,Dubbo中的擴(kuò)展都有一個(gè)別名,用于在應(yīng)用中引用它們。比如
random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
其中的random,roundrobin就是對(duì)應(yīng)擴(kuò)展的別名。這樣我們?cè)谂渲梦募惺褂胷andom或roundrobin就可以了。
一些路徑
和Java SPI從/META-INF/services目錄加載擴(kuò)展配置類似,Dubbo也會(huì)從以下路徑去加載擴(kuò)展配置文件:
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
Dubbo的LoadBalance擴(kuò)展點(diǎn)解讀
在了解了Dubbo的一些基本概念后,讓我們一起來看一個(gè)Dubbo中實(shí)際的擴(kuò)展點(diǎn),對(duì)這些概念有一個(gè)更直觀的認(rèn)識(shí)。
我們選擇的是Dubbo中的LoadBalance擴(kuò)展點(diǎn)。Dubbo中的一個(gè)服務(wù),通常有多個(gè)Provider,consumer調(diào)用服務(wù)時(shí),需要在多個(gè)Provider中選擇一個(gè)。這就是一個(gè)LoadBalance。我們一起來看看在Dubbo中,LoadBalance是如何成為一個(gè)擴(kuò)展點(diǎn)的。
LoadBalance接口
@SPI(RandomLoadBalance.NAME) public interface LoadBalance { @Adaptive("loadbalance") Invoker select(List> invokers, URL url, Invocation invocation) throws RpcException; }
LoadBalance接口只有一個(gè)select方法。select方法從多個(gè)invoker中選擇其中一個(gè)。上面代碼中和Dubbo SPI相關(guān)的元素有:
@SPI(RandomLoadBalance.NAME) @SPI作用于LoadBalance接口,表示接口LoadBalance是一個(gè)擴(kuò)展點(diǎn)。如果沒有@SPI注解,試圖去加載擴(kuò)展時(shí),會(huì)拋出異常。@SPI注解有一個(gè)參數(shù),該參數(shù)表示該擴(kuò)展點(diǎn)的默認(rèn)實(shí)現(xiàn)的別名。如果沒有顯示的指定擴(kuò)展,就使用默認(rèn)實(shí)現(xiàn)。RandomLoadBalance.NAME是一個(gè)常量,值是"random",是一個(gè)隨機(jī)負(fù)載均衡的實(shí)現(xiàn)。 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個(gè)LoadBalance的擴(kuò)展實(shí)現(xiàn)。由于負(fù)載均衡的實(shí)現(xiàn)不是本次的內(nèi)容,這里就不過多說明。只用知道Dubbo提供了4種負(fù)載均衡的實(shí)現(xiàn),我們可以通過xml文件,properties文件,JVM參數(shù)顯式的指定一個(gè)實(shí)現(xiàn)。如果沒有,默認(rèn)使用隨機(jī)。
@Adaptive("loadbalance") @Adaptive注解修飾select方法,表明方法select方法是一個(gè)可自適應(yīng)的方法。Dubbo會(huì)自動(dòng)生成該方法對(duì)應(yīng)的代碼。當(dāng)調(diào)用select方法時(shí),會(huì)根據(jù)具體的方法參數(shù)來決定調(diào)用哪個(gè)擴(kuò)展實(shí)現(xiàn)的select方法。@Adaptive注解的參數(shù)loadbalance表示方法參數(shù)中的loadbalance的值作為實(shí)際要調(diào)用的擴(kuò)展實(shí)例。 但奇怪的是,我們發(fā)現(xiàn)select的方法中并沒有l(wèi)oadbalance參數(shù),那怎么獲取loadbalance的值呢?select方法中還有一個(gè)URL類型的參數(shù),Dubbo就是從URL中獲取loadbalance的值的。這里涉及到Dubbo的URL總線模式,簡(jiǎn)單說,URL中包含了RPC調(diào)用中的所有參數(shù)。URL類中有一個(gè)Map parameters字段,parameters中就包含了loadbalance。
獲取LoadBalance擴(kuò)展
Dubbo中獲取LoadBalance的代碼如下:
LoadBalance lb = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(loadbalanceName);
使用ExtensionLoader.getExtensionLoader(LoadBalance.class)方法獲取一個(gè)ExtensionLoader的實(shí)例,然后調(diào)用getExtension,傳入一個(gè)擴(kuò)展的別名來獲取對(duì)應(yīng)的擴(kuò)展實(shí)例。
自定義一個(gè)LoadBalance擴(kuò)展
本節(jié)中,我們通過一個(gè)簡(jiǎn)單的例子,來自己實(shí)現(xiàn)一個(gè)LoadBalance,并把它集成到Dubbo中。我會(huì)列出一些關(guān)鍵的步驟和代碼,也可以從這個(gè)地址(https://github.com/vangoleo/d...。
實(shí)現(xiàn)LoadBalance接口
首先,編寫一個(gè)自己實(shí)現(xiàn)的LoadBalance,因?yàn)槭菫榱搜菔綝ubbo的擴(kuò)展機(jī)制,而不是LoadBalance的實(shí)現(xiàn),所以這里L(fēng)oadBalance的實(shí)現(xiàn)非常簡(jiǎn)單,選擇第一個(gè)invoker,并在控制臺(tái)輸出一條日志。
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); } }
添加擴(kuò)展配置文件
添加文件:META-INF/dubbo/com.alibaba.dubbo.rpc.cluster.LoadBalance。文件內(nèi)容如下:
demo=com.dubbo.spi.demo.consumer.DemoLoadBalance
配置使用自定義LoadBalance
通過上面的兩步,已經(jīng)添加了一個(gè)名字為demo的LoadBalance實(shí)現(xiàn),并在配置文件中進(jìn)行了相應(yīng)的配置。接下來,需要顯式的告訴Dubbo使用demo的負(fù)載均衡實(shí)現(xiàn)。如果是通過spring的方式使用Dubbo,可以在xml文件中進(jìn)行設(shè)置。
在consumer端的dubbo:reference中配置
啟動(dòng)Dubbo
啟動(dòng)Dubbo,調(diào)用一次IHelloService,可以看到控制臺(tái)會(huì)輸出一條DemoLoadBalance: Select the first invoker...日志。說明Dubbo的確是使用了我們自定義的LoadBalance。
總結(jié)
到此,我們從Java SPI開始,了解了Dubbo SPI 的基本概念,并結(jié)合了Dubbo中的LoadBalance加深了理解。最后,我們還實(shí)踐了一下,創(chuàng)建了一個(gè)自定義LoadBalance,并集成到Dubbo中。相信通過這里理論和實(shí)踐的結(jié)合,大家對(duì)Dubbo的可擴(kuò)展有更深入的理解。
總結(jié)一下,Dubbo SPI有以下的特點(diǎn):
? 對(duì)Dubbo進(jìn)行擴(kuò)展,不需要改動(dòng)Dubbo的源碼
? 自定義的Dubbo的擴(kuò)展點(diǎn)實(shí)現(xiàn),是一個(gè)普通的Java類,Dubbo沒有引入任何Dubbo特有的元素,對(duì)代碼侵入性幾乎為零。
? 將擴(kuò)展注冊(cè)到Dubbo中,只需要在ClassPath中添加配置文件。使用簡(jiǎn)單。而且不會(huì)對(duì)現(xiàn)有代碼造成影響。符合開閉原則。
? Dubbo的擴(kuò)展機(jī)制支持IoC,AoP等高級(jí)功能
? Dubbo的擴(kuò)展機(jī)制能很好的支持第三方IoC容器,默認(rèn)支持Spring Bean,可自己擴(kuò)展來支持其他容器,比如Google的Guice。
? 切換擴(kuò)展點(diǎn)的實(shí)現(xiàn),只需要在配置文件中修改具體的實(shí)現(xiàn),不需要改代碼。使用方便。
原文鏈接
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/69634.html
摘要:什么是類那什么樣類的才是擴(kuò)展機(jī)制中的類呢類是一個(gè)有復(fù)制構(gòu)造函數(shù)的類,也是典型的裝飾者模式。代碼如下有一個(gè)參數(shù)是的復(fù)制構(gòu)造函數(shù)有一個(gè)構(gòu)造函數(shù),參數(shù)是擴(kuò)展點(diǎn),所以它是一個(gè)擴(kuò)展機(jī)制中的類。 摘要:?在Dubbo可擴(kuò)展機(jī)制實(shí)戰(zhàn)中,我們了解了Dubbo擴(kuò)展機(jī)制的一些概念,初探了Dubbo中LoadBalance的實(shí)現(xiàn),并自己實(shí)現(xiàn)了一個(gè)LoadBalance。是不是覺得Dubbo的擴(kuò)展機(jī)制很不錯(cuò)呀...
摘要:架構(gòu)中有兩個(gè)主要角色服務(wù)提供者和服務(wù)使用者。服務(wù)提供者在啟動(dòng)時(shí),向注冊(cè)中心注冊(cè)自己提供的服務(wù)。負(fù)載平衡旨在優(yōu)化資源使用,最大化吞吐量,最小化響應(yīng)時(shí)間,并避免任何單個(gè)資源的過載。 本文來自于我的個(gè)人主頁(yè):Apache Dubbo,轉(zhuǎn)載請(qǐng)保留鏈接 ;) 在2011年10月27日,阿里巴巴開源了自己的SOA服務(wù)化治理方案的核心框架Dubbo,服務(wù)治理和SOA的設(shè)計(jì)理念開始逐漸在國(guó)內(nèi)軟件行業(yè)中...
摘要:今天的話題是與的開源現(xiàn)狀和未來規(guī)劃,我們知道,過去一段時(shí)間疏于維護(hù),去年阿里高調(diào)宣布重啟開源之后,社區(qū)里問的最多的問題是,這次開源與上次有什么一樣,還有就是和是什么關(guān)系希望通過這次的分享能夠解答這些問題。 摘要: Dubbo 在過去一段時(shí)間疏于維護(hù),去年阿里高調(diào)宣布重啟 Dubbo 開源之后,社區(qū)里問的最多的問題是,這次開源與上次有什么一樣,還有就是 Dubbo 和 Spring Bo...
摘要:服務(wù)提供者在啟動(dòng)時(shí),向注冊(cè)中心注冊(cè)自己提供的服務(wù)。注冊(cè)中心返回服務(wù)提供者地址列表給消費(fèi)者,如果有變更,注冊(cè)中心將基于長(zhǎng)連接推送變更數(shù)據(jù)給消費(fèi)者。 先來了解一下這些年架構(gòu)的變化,下面的故事是我編的。。。。 傳統(tǒng)架構(gòu):很多年前,剛學(xué)完JavaWeb開發(fā)的我憑借一人之力就開發(fā)了一個(gè)網(wǎng)站,網(wǎng)站 所有的功能和應(yīng)用都集中在一起,方便了我的開發(fā)同時(shí)也節(jié)省了成本。但是后來我的網(wǎng)站訪問流量突然加大,我通...
閱讀 3002·2021-10-27 14:16
閱讀 712·2021-10-13 09:39
閱讀 3727·2021-09-29 09:46
閱讀 2107·2019-08-30 15:54
閱讀 2611·2019-08-30 15:52
閱讀 3009·2019-08-30 15:44
閱讀 1120·2019-08-30 15:44
閱讀 511·2019-08-30 10:51