1、流程簡圖
dubbo的流程簡圖:java
2、通訊協議
dubbo支持多種通訊協議,包括:web
- dubbo協議(默認)。單一長鏈接(消費者和生產者一直保持單一的長鏈接)+ NIO異步通訊(provider做爲服務端輪詢socket)+ hessian序列化協議。適用傳輸數據量很小,可是併發量很高,消費者遠遠大於生產者。
- rmi協議。java序列化+短鏈接,適用於消費者和提供者數量差很少,適用於文件傳輸,通常較少用。
- hessian協議。hessian序列化 + 短鏈接。適用於生產者數量比消費者數量還多,多用於文件傳輸,通常較少用
- http協議。json序列化
- webservice。soap文本序列化
3、功能介紹
(一)負載均衡
可配置負載均衡策略有:redis
- random loadbalance(默認,隨機)。隨機,按權重設置隨機機率
- roundrobin loadbalance(輪詢)。按公約後的權重設置輪詢比率。存在慢的提供者累積請求的問題
- leastactive loadbalance(最少活躍調用數)。最小活躍數負載均衡。活躍調用數越小,代表該服務提供者效率越高,單位時間內可處理更多的請求。此時優先將請求分配給該服務提供者。在具體實現中,每一個服務提供者對應一個活躍數 active。初始狀況下,全部服務提供者活躍數均爲0。每收到一個請求,活躍數加1,完成請求後則將活躍數減1
- consistanthash loadbalance(一致性 Hash)。相同參數的請求老是發到同一提供者。
(二)集羣容錯
可配置的集羣容錯策略有:算法
- Faliover Cluster(默認)。失敗自動切換,當出現失敗,重試其它服務器。一般用於讀操做,但重試會帶來更長延遲。可經過 retries="2" 來設置重試次數(不含第一次)。
- Failfast Cluster。快速失敗,只發起一次調用,失敗當即報錯。一般用於非冪等性的寫操做,好比新增記錄。
- Failsafe Cluster。失敗安全,出現異常時,直接忽略。一般用於寫入審計日誌等操做。
- Failback Cluster。失敗自動恢復,後臺記錄失敗請求,定時重發。一般用於消息通知操做。
- Forking Cluster。並行調用多個服務器,只要一個成功即返回。一般用於實時性要求較高的讀操做,但須要浪費更多服務資源。可經過 forks="2" 來設置最大並行數。
- Broadcast Cluster。廣播調用全部提供者,逐個調用,任意一臺報錯則報錯。一般用於通知全部提供者更新緩存或日誌等本地資源信息。
(三)動態代理
dubbo動態生成代理類主要有兩種方式,一種是基於Javassist方式動態生成代理類(默認)-基於,另外一種是用jdk動態代理。我的也不是很瞭解Javassist、Cglib、JDK動態代理的底層原理區別。只能說這三種方式都能生成代理類,區別在於Javassist相比較而言比較靈活,API接近底層。JDK的動態代理限制較大,必須實現接口,Cglib生成的動態代理類直接是被代理類的父類。spring
(四)SPI機制-Service Provider Interface
SPI,個人理解就是框架方提供接口,使用方本身實現接口,提升框架擴展性的一個機制。意思就是框架方經過接口約定方法的職責實現了一套邏輯。並無明確代表這個接口必定要按照那種具體方式實現。因此就用SPI機制讓咱們本身能夠在不改變框架的基礎上去本身定義具體的實現。JDK提供了SPI機制的實現方式,可是dubbo不是直接使用的JDK實現的SPI,而是採用了本身的實現。下面介紹兩種方式的用法:數據庫
1.JDK實現SPI
第一步:在jar包或工程的meta-inf/service文件夾下,建立文件,文件名爲提供的接口名(包含全路徑)
第二步:編輯文件內容,文件內容爲實現類的全路徑。
第三步:框架方,加載實現類:
ServiceLoader<DriverService> serviceLoader = ServiceLoader.load(DriverService.class);
for (DriverService driverService: serviceLoader){
System.out.println(driverService.getName());
}
DriverService爲接口,遍歷獲得的是在Jar包內找到的實現類。json
二、Dubbbo實現SPI
名詞解釋:
- 擴展點,稱 Dubbo 中被 @SPI 註解的 Interface 爲一個擴展點。
- 擴展,被 @SPI 註解的 Interface 的實現稱爲這個擴展點的一個擴展。
註解解釋:
- @SPI:@SPI 註解標識了接口是一個擴展點 , 屬性 value 用來指定默認適配擴展點的名稱。
- @Activate:@Activate 註解在擴展點的實現類上 ,表示了一個擴展類被獲取到的的條件,符合條件就被獲取,不符合條件就不獲取 ,根據 @Activate 中的 group 、value 屬性來過濾。
- @Adaptive:@Adaptive 註解在類上,這個類就是缺省的適配擴展。@Adaptive 註解在擴展點 Interface 的方法上時 ,dubbo動態的生成一個這個擴展點的適配擴展類(生成代碼 ,動態編譯實例化 Class ),名稱爲擴展點 Interface 的簡單類名 + $Adaptive ,例如 : ProxyFactory$Adpative 。這麼作的目的是爲了在運行時去適配不一樣的擴展實例 , 在運行時經過傳入的 URL 類型的參數或者內部含有獲取 URL 方法的參數 ,從 URL 中獲取到要使用的擴展類的名稱 ,再去根據名稱加載對應的擴展實例 ,用這個擴展實例對象調用相同的方法 。若是運行時沒有適配到運行的擴展實例 ,那麼就使用 @SPI 註解缺省指定的擴展。經過這種方式就實現了運行時去適配到對應的擴展。
- 注意: 若@Adaptive標誌了實現類,和@SPI標誌的默認實現Key,以及SPI接口方法上也標了 @Adaptive接受Url參數解析實現類,優先級次序是 @Adaptive標誌的實現類大於Url參數大於@SPI標誌的默認實現.
擴展dubbo框架中SPI接口的方式
dubbo自帶了一些spi接口,這裏介紹下怎麼實現這些SPI接口。緩存
路徑能夠是:META-INF/services/(擴展點接口的全類名)、 META-INF/dubbo/(擴展點接口的全類名 )、 META-INF/dubbo/internal/(擴展點接口的全類名)。安全
文件內容,鍵爲擴展名,值爲擴展實現類路徑,相似:服務器
adaptive=com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory
spi=com.alibaba.dubbo.common.extension.factory.SpiExtensionFactory
spring=com.alibaba.dubbo.config.spring.extension.SpringExtensionFactory
如 <dubbo:protocol name='my' prot="20000"/> 能夠修改protocol的實現爲指定擴展名爲my的擴展。
dubbo框架中的可擴展點:
包括動態代理方式(ProxyFactory)、負載均衡策略(LoadBalance)、RPC協議(Protocol)、攔截器(Filter)、容器類型(Container)、集羣方式(Cluster)和註冊中心類型(RegistryFactory)等。
(五)服務降級
配置方式例
消費者配置文件:
<dubbo:reference id="iUser" interface="com.dubbosample.iface.IUser" timeout="10000" check="false" mock="return null">
</dubbo:reference>
其中,關鍵屬性爲mock,其有如下幾種屬性值:
- false,不使用mock
- true/default/fail,mock調用名爲接口+Impl類的對應mock方法
- {mockClass},mock調用${mockClass}對應方法
- return xxx,直接返回xxx的Mock數據,xxx支持json數據
- throw xxxException,直接拋出異常
- force xxx xxx爲接口實現類類名,mock表達式只含有force的話,直接走接口相同路徑下類名爲「接口+Impl類"對應相同方法的邏輯,不掉接口,至關於直接屏蔽接口。
(六)服務重試
配置方式例
生產者端:
- 使用註解:@Service(retries = 1,timeout = 2000)。
- 使用配置:<dubbo:provider retries="0" timeout="3000"/>
4、可能致使的問題
(一)冪等性問題
一些寫操做接口須要保證冪等性,特別是包含支付邏輯的接口。
1.場景
- 單機接收到重複請求
- 相同服務不一樣機器接收到重複請求
2.解決方案
思路
針對每一個請求都須要標識一個惟一id,每次處理以後必須有一個記錄標識這個請求處理過,每次接收到請求時判斷是否處理過,若是處理過就直接忽略。
具體
- 基於數據庫惟一索引
- 單機可直接基於本地MAP或SET
- 分佈式的能夠基於緩存如redis
(二)順序性問題
針對順序性的業務,好比先修改後刪除的操做,若是執行順序錯了,那麼業務就錯了。
1.場景
消費者異步調用的生產者的一組服務接口沒有阻塞等待確保順序問題。
2.解決方案
- 使用一致性hash算法負載均衡策略。若這一組服務接口位於同一個服務,能夠用一致性hash算法負載均衡策略使得調用的服務被分發到一臺機器上,而後這臺機器又採起內存隊列的形式進行消費。
- 使用分佈式鎖機制。鎖的key爲對應業務單號,值爲順序號,每一個生產者服務接口獲取鎖的時候判斷下業務Key的value中的順序值而後判斷是否是該輪到本身。如果則獲取鎖。若不是則阻塞一段時間再去獲取鎖。
5、小結一下設計rpc框架的思路
好比zookeeper,能夠爲每個接口的每個方法註冊一個臨時節點,而後key爲接口方法的惟一標識(包含class路徑、方法名稱,參數簽名),data爲服務地址列表
調用服務應該設計爲動態代理,該動態代理類處理,拉取服務信息、負載均衡、序列化參數、發送請求。具體能夠設計爲先根據調用的接口去查本地緩存有沒有該服務地址列表,若是有直接用必定的算法好比輪詢取其中之一的地址,而後地址有了以後,將參數和請求id(由於須要將獲取的響應關聯請求Id)封裝爲一個對象好比名叫Invoker類,選擇必定的序列化協議將數據發送給生產者,同時將Invoker對象放入一個本地內存併發容器中如名叫inProgressInvoker,用requestId做爲Key,而後用Invoker對象的wait方法阻塞本身(這裏先配置發送通訊方式可使用netty,設定接收到返回的回調方法,接收到生產者響應以後首先壓回隊列而後經過notifyAll喚醒後臺隊列的消費線程(1個或多個,能夠經過參數配置))。
後臺消費線程的設計是先從隊列取出消息(若是沒有取到,則調用上面回調方法裏notifyAll中的鎖資源的wait方法阻塞本身),若是有消息,則先反序列化接收到生產者返回的內容,而後根據requestId去上面的inProgressInvoker取出Invoker對象,將響應設置到Invoker對象中,同時調用notifyAll或notify方法,喚醒消費那裏被阻塞的主線程,
生產者這邊的netty監聽到事件以後,經過線程池處理請求,將請求數據反序列化解析爲對象,經過接口方法惟一標識,以及參數信息來反射調用真正的接口實現類,處理好以後連帶請求id在經過必定序列化協議返回給消費者。