目錄
? ? 1 應(yīng)用架構(gòu)演變
? ? 2 RPC
? ? 3 Dubbo概述
? ? 4 Dubbo配置
? ? 5 Dubbo協(xié)議
? ? 6 高可用
? ? 7 Dubbo原理
? ? 8 Dubbo源碼分析
? ? 9 Dubbox
? ? 10 基于Spring的Dubbo整合
? ? 11 常見面試題總結(jié)參考目錄
? ? · 尚硅谷Dubbo
? ? · CSDN
1 應(yīng)用架構(gòu)演變
1.1 單一應(yīng)用架構(gòu)
????當網(wǎng)站流量很小時,只需一個應(yīng)用,將所有功能都部署在一起,以減少部署節(jié)點和成本。?此時,用于簡化增刪改查工作量的數(shù)據(jù)訪問框架(ORM) 是關(guān)鍵。
1.2 垂直應(yīng)用架構(gòu)
????當訪問量逐漸增大,單一應(yīng)用增加機器帶來的加速度越來越小,將應(yīng)用拆成互不相干的幾個應(yīng)用,以提升效率。?此時,用于加速前端頁面開發(fā)的Web框架(MVC) 是關(guān)鍵。
1.3 分布式服務(wù)架構(gòu)
????當垂直應(yīng)用越來越多,應(yīng)用之間交互不可避免,將核心業(yè)務(wù)抽取出來,作為獨立的服務(wù),逐漸形成穩(wěn)定的服務(wù)中心,使前端應(yīng)用能更快速的響應(yīng)多變的市場需求。?此時,用于提高業(yè)務(wù)復(fù)用及整合的分布式服務(wù)框架(RPC) 是關(guān)鍵。
1.4 流動計算架構(gòu)
????當服務(wù)越來越多,容量的評估,小服務(wù)資源的浪費等問題逐漸顯現(xiàn),此時需增加一個調(diào)度中心基于訪問壓力實時管理集群容量,提高集群利用率。?此時,用于提高機器利用率的資源調(diào)度和治理中心(SOA) 是關(guān)鍵。
2 RPC
2.1 定義
? ??RPC【Remote Procedure Call】是指遠程過程調(diào)用,是一種進程間通信方式,他是一種技術(shù)的思想,而不是規(guī)范。它允許程序調(diào)用另一個地址空間(通常是共享網(wǎng)絡(luò)的另一臺機器上)的過程或函數(shù),而不用程序員顯式編碼這個遠程調(diào)用的細節(jié)。
? ? 另外,總結(jié):詳解RMI與RPC的區(qū)別 - 知乎
2.2 結(jié)構(gòu)
????RPC 服務(wù)方通過 RpcServer 去導(dǎo)出(export)遠程接口方法,而客戶方通過 RpcClient 去引入(import)遠程接口方法。客戶方像調(diào)用本地方法一樣去調(diào)用遠程接口方法,RPC 框架提供接口的代理實現(xiàn),實際的調(diào)用將委托給代理RpcProxy 。代理封裝調(diào)用信息并將調(diào)用轉(zhuǎn)交給RpcInvoker 去實際執(zhí)行。在客戶端的RpcInvoker 通過連接器RpcConnector 去維持與服務(wù)端的通道RpcChannel,并使用RpcProtocol 執(zhí)行協(xié)議編碼(encode)并將編碼后的請求消息通過通道發(fā)送給服務(wù)方。
? ? 具體功能如下:
(1)RpcServer? 負責導(dǎo)出(export)遠程接口?
(2)RpcClient? ?負責導(dǎo)入(import)遠程接口的代理實現(xiàn)?
(3)RpcProxy? ?遠程接口的代理實現(xiàn)?
(4)RpcInvoker?
? ???????? 客戶方實現(xiàn):負責編碼調(diào)用信息和發(fā)送調(diào)用請求到服務(wù)方并等待調(diào)用結(jié)果返回?
? ???????? 服務(wù)方實現(xiàn):負責調(diào)用服務(wù)端接口的具體實現(xiàn)并返回調(diào)用結(jié)果?
(5)RpcProtocol? ? ? 負責協(xié)議編/解碼?
(6)RpcConnector? 負責維持客戶方和服務(wù)方的連接通道和發(fā)送數(shù)據(jù)到服務(wù)方?
(7)RpcAcceptor? ? ?負責接收客戶方請求并返回請求結(jié)果?
(8)RpcProcessor? ?負責在服務(wù)方控制調(diào)用過程,包括管理調(diào)用線程池、超時時間等?
(9)RpcChannel? ? ? 數(shù)據(jù)傳輸通道
2.3 工作原理
(1)Client像調(diào)用本地服務(wù)似的調(diào)用遠程服務(wù);
(2)Client stub接收到調(diào)用后,將方法、參數(shù)序列化
(3)客戶端通過sockets將消息發(fā)送到服務(wù)端
(4)Server stub 收到消息后進行解碼(將消息對象反序列化)
(5)Server stub 根據(jù)解碼結(jié)果調(diào)用本地的服務(wù)
(6)本地服務(wù)執(zhí)行(對于服務(wù)端來說是本地執(zhí)行)并將結(jié)果返回給Server stub
(7)Server stub將返回結(jié)果打包成消息(將結(jié)果消息對象序列化)
(8)服務(wù)端通過sockets將消息發(fā)送到客戶端
(9)Client stub接收到結(jié)果消息,并進行解碼(將結(jié)果消息發(fā)序列化)
(10)客戶端得到最終結(jié)果。
· 關(guān)于對stub的理解
????????stub:RPC(Remote Procedure Call protocol)的一個重要思想就是使遠程調(diào)用看起來象當?shù)氐恼{(diào)用一樣,也就是說調(diào)用進程無需知道被調(diào)進程具體在哪臺機器上執(zhí)行。Stub就是用來保證此特性的很重要的部分。具體的講,比如在客戶端,一個進程在執(zhí)行過程中調(diào)用到了某個函數(shù)fn(),此函數(shù)的具體實現(xiàn)是在遠程的某臺機器上,那么此進程實際上是調(diào)用了位于當?shù)貦C器上的另外一個版本的fn()(起名為c_fn()),此c_fn()就是客戶端的一個stub. 對應(yīng)的,當客戶端的消息發(fā)送到服務(wù)器端時,服務(wù)器端也不是把消息直接就交給真正的fn(),而是同樣先交給一個不同版本的fn()(起名為s_fn()),此s_fn()就是服務(wù)器端的一個stub.?
2.4 工作方式
(1)同步調(diào)用:客戶方等待調(diào)用執(zhí)行完成并返回結(jié)果。
(2)異步調(diào)用:客戶方調(diào)用后不用等待執(zhí)行結(jié)果返回,但依然可以通過回調(diào)通知等方式獲取返回結(jié)果。若客戶方不關(guān)心調(diào)用返回結(jié)果,則變成單向異步調(diào)用,單向調(diào)用不用返回結(jié)果。
3 Dubbo概述
3.1 概念
? ??Dubbo是一個分布式服務(wù)框架,致力于提供高性能和透明化的RPC遠程服務(wù)調(diào)用方案,SOA服務(wù)治理方案。
????簡單的說,dubbo就是個服務(wù)框架,如果沒有分布式的需求,其實是不需要用的,只有在分布式的時候,才有dubbo這樣的分布式服務(wù)框架的需求,并且本質(zhì)上是個服務(wù)調(diào)用,說白了就是個遠程服務(wù)調(diào)用的分布式框架。
3.2 三大核心能力
(1)遠程通訊,提供對多種基于長連接的NiO框架抽象封裝,包括多種線程模型,序列化,以及“請求一響應(yīng)”模式的信息交換方式。
(2)集群容錯: 提供基于接口方法的透明遠程過程調(diào)用,包括多協(xié)議支持。以及負載均衡,失敗容錯,地址路由,動態(tài)配置等集群支持。
(3)自動發(fā)現(xiàn):基于注冊中心目錄服務(wù),使用服務(wù)消費能動態(tài)查找服務(wù)提供方,使地址透明,使用服務(wù)提供方可以平滑增加或減少服務(wù)器
3.3 架構(gòu)
(1)服務(wù)提供者(Provider):暴露服務(wù)的服務(wù)提供方,服務(wù)提供者在啟動時,向注冊中心注冊自己提供的服務(wù)。
(2)服務(wù)消費者(Consumer):調(diào)用遠程服務(wù)的服務(wù)消費方,服務(wù)消費者在啟動時,向注冊中心訂閱自己所需的服務(wù),服務(wù)消費者,從提供者地址列表中,基于軟負載均衡算法,選一臺提供者進行調(diào)用,如果調(diào)用失敗,再選另一臺調(diào)用。
(3)注冊中心(Registry):注冊中心返回服務(wù)提供者地址列表給消費者,如果有變更,注冊中心將基于長連接推送變更數(shù)據(jù)給消費者
(4)監(jiān)控中心(Monitor):服務(wù)消費者和提供者,在內(nèi)存中累計調(diào)用次數(shù)和調(diào)用時間,定時每分鐘發(fā)送一次統(tǒng)計數(shù)據(jù)到監(jiān)控中心
3.4 調(diào)用關(guān)系
(1)服務(wù)容器負責啟動,加載,運行服務(wù)提供者。
(2)服務(wù)提供者在啟動時,向注冊中心注冊自己提供的服務(wù)。
(3)服務(wù)消費者在啟動時,向注冊中心訂閱自己所需的服務(wù)。
(4)注冊中心返回服務(wù)提供者地址列表給消費者,如果有變更,注冊中心將基于長連接推送變更數(shù)據(jù)給消費者。
(5)服務(wù)消費者,從提供者地址列表中,基于軟負載均衡算法,選一臺提供者進行調(diào)用,如果調(diào)用失敗,再選另一臺調(diào)用。
(6)服務(wù)消費者和提供者,在內(nèi)存中累計調(diào)用次數(shù)和調(diào)用時間,定時每分鐘發(fā)送一次統(tǒng)計數(shù)據(jù)到監(jiān)控中心。
3.5 注冊中心
(1)可選方案:zookeeper、Redis
(2)建議使用dubbo-2.3.3以上版本的使用zookeeper注冊中心客戶端
????????·Zookeeper是Apache Hadoop的子項目,強度相對較好,建議生產(chǎn)環(huán)境使用該注冊中心。
????????· Dubbo未對Zookeeper服務(wù)器端做任何侵入修改,只需安裝原生的Zookeeper服務(wù)器即可, 所有注冊中心邏輯適配都在調(diào)用Zookeeper客戶端時完成
? ? ? ? · Dubbo使用ZooKeeper發(fā)布服務(wù)時,使用的是ZooKeeper的持久節(jié)點
? ? 具體如何通過Zookeeper和Redis構(gòu)建,參考:Dubbo(一)——Dubbo及注冊中心原理_baoyu_G的博客-CSDN博客_dubbo注冊
3.6 Dubbo的特性
(1)健狀性:
? ? ? ? · 監(jiān)控中心宕掉不影響使用,只是丟失部分采樣數(shù)據(jù)
? ? ? ? · 數(shù)據(jù)庫宕掉后,注冊中心仍能通過緩存提供服務(wù)列表查詢,但不能注冊新服務(wù)
? ? ? ? · 注冊中心對等集群,任意一臺宕掉后,將自動切換到另一臺
? ? ? ? ·?注冊中心全部宕掉后,服務(wù)提供者和服務(wù)消費者仍能通過本地緩存通訊
? ? ? ? · 服務(wù)提供者無狀態(tài),任意一臺宕掉后,不影響使用
? ? ? ? · 服務(wù)提供者全部宕掉后,服務(wù)消費者應(yīng)用將無法使用,并無限次重連等待服務(wù)提供者恢復(fù)
(2)伸縮性:
????????注冊中心為對等集群,可動態(tài)增加機器部署實例,所有客戶端將自動發(fā)現(xiàn)新的注冊中心?
????????服務(wù)提供者無狀態(tài),可動態(tài)增加機器部署實例,注冊中心將推送新的服務(wù)提供者信息給消費者
(3)升級性:
????????當服務(wù)集群規(guī)模進一步擴大,帶動IT治理結(jié)構(gòu)進一步升級,需要實現(xiàn)動態(tài)部署,進行流動計算,現(xiàn)有分布式服務(wù)架構(gòu)不會帶來阻力。
3.7 Dubbo-admin管理平臺
(1)圖形化的服務(wù)管理頁面;安裝時需要指定注冊中心地址,即可從注冊中心中獲取到所有的提供者/消費者進行配置管理,從而進行服務(wù)治理。
(2)主要包含
????????·路由規(guī)則
????????·動態(tài)配置
????????·服務(wù)降級
????????·訪問控制
????????·權(quán)重調(diào)整 負載均衡等管理功能
4 Dubbo配置
4.1 配置原則
· JVM啟動 -D 參數(shù)優(yōu)先,這樣可以使用戶在部署和啟動時進行參數(shù)重寫,比如在啟動時需改變協(xié)議的端口。
· XML次之,如果在 XML 中有配置,則 dubbo.properties 中的相應(yīng)配置項無效。
· Properties最后,相當于缺省值,只有 XML 沒有配置時,dubbo.properties 的相應(yīng)配置項才會生效,通常用于共享公共配置,比如應(yīng)用名。
4.2 重試次數(shù)
????失敗自動切換,當出現(xiàn)失敗,重試其它服務(wù)器,但重試會帶來更長延遲。可通過retries="2"來設(shè)置重試次數(shù)(不含第一次)。
· 重試次數(shù)配置如下:
<dubbo:service retries="2" />
或
<dubbo:reference retries="2" />
或
<dubbo:reference>
????<dubbo:method name="findFoo" retries="2" />
</dubbo:reference>
4.3 超時時間
????由于網(wǎng)絡(luò)或服務(wù)端不可靠,會導(dǎo)致調(diào)用出現(xiàn)一種不確定的中間狀態(tài)(超時)。為了避免超時導(dǎo)致客戶端資源(線程)掛起耗盡,必須設(shè)置超時時間。
(1)Dubbo消費端
全局超時配置
<dubbo:consumer timeout="5000" />
指定接口以及特定方法超時配置
<dubbo:reference interface="com.foo.BarService" timeout="2000">
????<dubbo:method name="sayHello" timeout="3000" />
</dubbo:reference>
(2)Dubbo服務(wù)端
全局超時配置
<dubbo:provider timeout="5000" />
指定接口以及特定方法超時配置
<dubbo:provider interface="com.foo.BarService" timeout="2000">
????<dubbo:method name="sayHello" timeout="3000" />
</dubbo:provider>
(3)配置原則
????dubbo推薦在Provider上盡量多配置Consumer端屬性:
1、作服務(wù)的提供者,比服務(wù)使用方更清楚服務(wù)性能參數(shù),如調(diào)用的超時時間,合理的重試次數(shù),等等
2、在Provider配置后,Consumer不配置則會使用Provider的配置值,即Provider配置可以作為Consumer的缺省值。否則,Consumer會使用Consumer端的全局設(shè)置,這對于Provider不可控的,并且往往是不合理的
配置的覆蓋規(guī)則:
? ? · 方法級配置別優(yōu)于接口級別,即小Scope優(yōu)先
? ? · Consumer端配置 優(yōu)于 Provider配置 優(yōu)于 全局配置,
? ? · 最后是Dubbo Hard Code的配置值(見配置文檔)
4.4 版本號
(1)當一個接口實現(xiàn),出現(xiàn)不兼容升級時,可以用版本號過渡,版本號不同的服務(wù)相互間不引用。
(2)可以按照以下的步驟進行版本遷移:
? ? · 在低壓力時間段,先升級一半提供者為新版本
? ? · 再將所有消費者升級為新版本
? ? · 然后將剩下的一半提供者升級為新版本
·老版本服務(wù)提供者配置:
????<dubbo:service interface="com.foo.BarService" version="1.0.0" />
·新版本服務(wù)提供者配置:
????<dubbo:service interface="com.foo.BarService" version="2.0.0" />
·老版本服務(wù)消費者配置:
????<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />
·新版本服務(wù)消費者配置:
????<dubbo:reference id="barService" interface="com.foo.BarService" version="2.0.0" />
·如果不需要區(qū)分版本,可以按照以下的方式配置:
????<dubbo:reference id="barService" interface="com.foo.BarService" version="*" />
5 Dubbo協(xié)議
5.1 Dubbo協(xié)議
Dubbo缺省協(xié)議采用單一長連接和NIO異步通訊。
????適合于小數(shù)據(jù)量大并發(fā)的服務(wù)調(diào)用,以及服務(wù)消費者機器數(shù)遠大于服務(wù)提供者機器數(shù)的情況。Dubbo缺省協(xié)議不適合傳送大數(shù)據(jù)量的服務(wù),比如傳文件,傳視頻等,除非請求量很低。
5.2 Hessian協(xié)議
(1)Hessian協(xié)議用于集成Hessian的服務(wù),Hessian底層采用Http通訊,采用Servlet暴露服務(wù),Dubbo缺省內(nèi)嵌Jetty作為服務(wù)器實現(xiàn)。Hessian是Caucho開源的一個RPC框架:
(2)基于Hessian的遠程調(diào)用協(xié)議特點
? ? ? ? · 連接個數(shù):多連接
? ? ? ? · 連接方式:短連接
? ? ? ? · 傳輸協(xié)議:HTTP
? ? ? ? · 傳輸方式:同步傳輸
? ? ? ? · 序列化:Hessian二進制序列化
? ? ? ? · 適用范圍:傳入傳出參數(shù)數(shù)據(jù)包較大,提供者比消費者個數(shù)多,提供者壓力較大,可傳文件。
? ? ? ? · 適用場景:頁面?zhèn)鬏敚募鬏敚蚺c原生hessian服務(wù)互操作
5.3 HTTP協(xié)議
(1)此協(xié)議采用spring的HttpInvoker的功能實現(xiàn)
(2)特點
? ? ? ? · 連接個數(shù):多個
? ? ? ? · 連接方式:長連接
? ? ? ? · 連接協(xié)議:http
? ? ? ? · 傳輸方式:同步傳輸
? ? ? ? · 序列化:表單序列化
? ? ? ? · 適用范圍:傳入傳出參數(shù)數(shù)據(jù)包大小混合,提供者比消費者個數(shù)多,可用瀏覽器查看,可用表單或URL傳入?yún)?shù),暫不支持傳文件。
? ? ? ? · 適用場景:需同時給應(yīng)用程序和瀏覽器JS使用的服務(wù)。
5.4 RMI協(xié)議
(1)采用JDK標準的java.rmi.*實現(xiàn),采用阻塞式短連接和JDK標準序列化方式
(2)Java標準的遠程調(diào)用協(xié)議:
? ? ? ? · 連接個數(shù):多連接
? ? ? ? · 連接方式:短連接
? ? ? ? · 傳輸協(xié)議:TCP
? ? ? ? · 傳輸方式:同步傳輸
? ? ? ? · 序列化:Java標準二進制序列化
? ? ? ? · 適用范圍:傳入傳出參數(shù)數(shù)據(jù)包大小混合,消費者與提供者個數(shù)差不多,可傳文件。
? ? ? ? · 適用場景:常規(guī)遠程服務(wù)方法調(diào)用,與原生RMI服務(wù)互操作
5.5?webservice
????基于WebService的遠程調(diào)用協(xié)議,集成CXF實現(xiàn),提供和原生WebService的互操作。多個短連接,基于HTTP傳輸,同步傳輸,適用系統(tǒng)集成和跨語言調(diào)用;
6 高可用
6.1 ZooKeeper宕機與Dubbo直連
(1)現(xiàn)象:zookeeper注冊中心宕機,還可以消費dubbo暴露的服務(wù)。
(2)原因:健壯性
6.2 集群下dubbo負載均衡配置
(1)在集群負載均衡時,Dubbo提供了多種均衡策略,缺省為 random 隨機調(diào)用
(2)負載均衡策略
? ? ·?Random LoadBalance??隨機,按權(quán)重設(shè)置隨機概率。
????????在一個截面上碰撞的概率高,但調(diào)用量越大分布越均勻,而且按概率使用權(quán)重后也比較均勻,有利于動態(tài)調(diào)整提供者權(quán)重。
? ? ·?RoundRobin LoadBalance?輪循,按公約后的權(quán)重設(shè)置輪循比率。
????????存在慢的提供者累積請求的問題,比如:第二臺機器很慢,但沒掛,當請求調(diào)到第二臺時就卡在那,久而久之,所有請求都卡在調(diào)到第二臺上。
? ? ·?LeastActive LoadBalance?最少活躍調(diào)用數(shù),相同活躍數(shù)的隨機
????????活躍數(shù)指調(diào)用前后計數(shù)差。
????????使慢的提供者收到更少請求,因為越慢的提供者的調(diào)用前后計數(shù)差會越大。
? ? ·?ConsistentHash LoadBalance?一致性Hash,相同參數(shù)的請求總是發(fā)到同一提供者
????????當某一臺提供者掛時,原本發(fā)往該提供者的請求,基于虛擬節(jié)點,平攤到其它提供者,不會引起劇烈變動。
缺省只對第一個參數(shù)Hash,如果要修改,請配置
????<dubbo:parameter key="hash.arguments" value="0,1" />缺省用160 份虛擬節(jié)點,如果要修改,請配置
????<dubbo:parameter key="hash.nodes" value="320" />
6.3 整合Hystrix
6.3.1 服務(wù)降級
(1)概念
? ??當服務(wù)器壓力劇增的情況下,根據(jù)實際業(yè)務(wù)情況及流量,對一些服務(wù)和頁面有策略的不處理或換種簡單的方式處理,從而釋放服務(wù)器資源以保證核心交易正常運作或高效運作。
????可以通過服務(wù)降級功能臨時屏蔽某個出錯的非關(guān)鍵服務(wù),并定義降級后的返回策略。
(2)向注冊中心寫入動態(tài)配置覆蓋規(guī)則:
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"));
其中:
? ? · mock=force:return+null表示消費方對該服務(wù)的方法調(diào)用都直接返回 null 值,不發(fā)起遠程調(diào)用。用來屏蔽不重要服務(wù)不可用時對調(diào)用方的影響。
? ? · 還可以改為mock=fail:return+null表示消費方對該服務(wù)的方法調(diào)用在失敗后,再返回 null 值,不拋異常。用來容忍不重要服務(wù)不穩(wěn)定時對調(diào)用方的影響。
6.3.2 集群容錯
????在集群調(diào)用失敗時,Dubbo提供了多種容錯方案,缺省為 failover 重試。
· 集群容錯模式
(1)Failover Cluster
????????失敗自動切換,當出現(xiàn)失敗,重試其它服務(wù)器。通常用于讀操作,但重試會帶來更長延遲。可通過retries="2" 來設(shè)置重試次數(shù)(不含第一次)。
重試次數(shù)配置如下:
<dubbo:service retries="2" />
或
<dubbo:reference retries="2" />
或
<dubbo:reference>
????<dubbo:method name="findFoo" retries="2" />
</dubbo:reference>
(2)Failfast Cluster
????快速失敗,只發(fā)起一次調(diào)用,失敗立即報錯。通常用于非冪等性的寫操作,比如新增記錄。
(3)Failsafe Cluster
????失敗安全,出現(xiàn)異常時,直接忽略。通常用于寫入審計日志等操作。
(4)Failback Cluster
????失敗自動恢復(fù),后臺記錄失敗請求,定時重發(fā)。通常用于消息通知操作。
(5)Forking Cluster
????并行調(diào)用多個服務(wù)器,只要一個成功即返回。通常用于實時性要求較高的讀操作,但需要浪費更多服務(wù)資源。可通過forks="2" 來設(shè)置最大并行數(shù)。
(6)Broadcast Cluster
????廣播調(diào)用所有提供者,逐個調(diào)用,任意一臺報錯則報錯[2]。通常用于通知所有提供者更新緩存或日志等本地資源信息。
· 集群模式配置
按照以下示例在服務(wù)提供方和消費方配置集群模式
<dubbo:service cluster="failsafe" />
或
<dubbo:reference cluster="failsafe" />
6.3.3?整合hystrix
????Hystrix旨在通過控制那些訪問遠程系統(tǒng)、服務(wù)和第三方庫的節(jié)點,從而對延遲和故障提供更強大的容錯能力。Hystrix具備擁有回退機制和斷路器功能的線程和信號隔離,請求緩存和請求打包,以及監(jiān)控和配置等功能
(1)配置spring-cloud-starter-netflix-hystrix
????spring boot官方提供了對hystrix的集成,直接在pom.xml里加入依賴:
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.springframework.cloud</groupId>
? ? ? ? ? ? <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
? ? ? ? ? ? <version>1.4.4.RELEASE</version>
? ? ? ? </dependency>
????然后在Application類上增加@EnableHystrix來啟用hystrix starter:
@SpringBootApplication
@EnableHystrix
public class ProviderApplication {
(2)配置Provider端
????在Dubbo的Provider上增加@HystrixCommand配置,這樣子調(diào)用就會經(jīng)過Hystrix代理。
@Service(version = "1.0.0")
public class HelloServiceImpl implements HelloService {
? ? @HystrixCommand(commandProperties = {
? ? ???? @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
? ????? @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") })
? ? @Override
? ? public String sayHello(String name) {
? ? ? ? // System.out.println("async provider received: " + name);
? ? ? ? // return "annotation: hello, " + name;
? ? ? ? throw new RuntimeException("Exception to show hystrix enabled.");
? ? }
}
(3)配置Consumer端
????對于Consumer端,則可以增加一層method調(diào)用,并在method上配置@HystrixCommand。當調(diào)用出錯時,會走到fallbackMethod = "reliable"的調(diào)用里。
? ? @Reference(version = "1.0.0")
? ? private HelloService demoService;
? ? @HystrixCommand(fallbackMethod = "reliable")
? ? public String doSayHello(String name) {
? ? ? ? return demoService.sayHello(name);
? ? }
? ? public String reliable(String name) {
? ? ? ? return "hystrix fallback value";
? ? }
7 Dubbo原理
7.1 是一個RPC遠程調(diào)用框架
????具體可見第2小節(jié)
7.2 Netty
? ? Dubbo底層采用Netty作為網(wǎng)絡(luò)通信。
7.2.1 概念
????Netty是一個異步事件驅(qū)動的網(wǎng)絡(luò)應(yīng)用程序框架,用于快速開發(fā)可維護的高性能協(xié)議服務(wù)器和客戶端。它極大地簡化并簡化了TCP和UDP套接字服務(wù)器等網(wǎng)絡(luò)編程。
? ? 同時是一個基于JAVA NIO 類庫的異步通信框架,它的架構(gòu)特點是:異步非阻塞、基于事件驅(qū)動、高性能、高可靠性和高可定制性。
7.2.2 應(yīng)用場景
(1)分布式開源框架中dubbo、Zookeeper,RocketMQ底層rpc通訊使用就是netty。
(2)游戲開發(fā)中,底層使用netty通訊。
7.2.3 TCP粘包和拆包
(1)概念
????????一個完整的業(yè)務(wù)可能會被TCP拆分成多個包進行發(fā)送,也有可能把多個小的包封裝成一個大的數(shù)據(jù)包發(fā)送,這個就是TCP的拆包和封包問題。
(2)由于網(wǎng)絡(luò)的復(fù)雜性,可能數(shù)據(jù)會被分離成N多個復(fù)雜的拆包/粘包的情況,所以在做TCP服務(wù)器的時候就需要首先解決該問題。
? ? · 消息定長,報文大小固定長度,不夠空格補全,發(fā)送和接收方遵循相同的約定,這樣即使粘包了通過接收方編程實現(xiàn)獲取定長報文也能區(qū)分。
sc.pipeline().addLast(new FixedLengthFrameDecoder(10));
? ? · 包尾添加特殊分隔符,例如每條報文結(jié)束都添加回車換行符(例如FTP協(xié)議)或者指定特殊字符作為報文分隔符,接收方通過特殊分隔符切分報文區(qū)分。
ByteBuf buf?= Unpooled.copiedBuffer("_mayi".getBytes());
sc.pipeline().addLast(new?DelimiterBasedFrameDecoder(1024, buf));
? ? · 將消息分為消息頭和消息體,消息頭中包含表示信息的總長度(或者消息體長度)的字段
7.2.4 序列化協(xié)議
(1)一種序列化協(xié)議就是Java默認提供的序列化機制,需要序列化的Java對象只需要實現(xiàn) Serializable / Externalizable 接口并生成序列化ID,這個類就能夠通過 ObjectInput 和 ObjectOutput 序列化和反序列化
(2)XML
? ? · 定義:
????????XML(Extensible Markup Language)是一種常用的序列化和反序列化協(xié)議, 它歷史悠久,從1998年的1.0版本被廣泛使用至今。
? ? · 優(yōu)點
? ? ? ? · 人機可讀性好
? ? ? ?·? 可指定元素或特性的名稱
? ? · 缺點
? ? ? ? · 序列化數(shù)據(jù)只包含數(shù)據(jù)本身以及類的結(jié)構(gòu),不包括類型標識和程序集信息。
? ? ? ? · 類必須有一個將由XmlSerializer序列化的默認構(gòu)造函數(shù)。
? ? ? ? · 只能序列化公共屬性和字段
? ? ? ? · 不能序列化方法
? ? ? ? · 文件龐大,文件格式復(fù)雜,傳輸占帶寬
? ? · 使用場景
? ? ? ? · 當做配置文件存儲數(shù)據(jù)
? ? ? ? · 實時數(shù)據(jù)轉(zhuǎn)換
(3)JSON
? ? · 定義:
????????JSON(JavaScript Object Notation, JS對象標記) 是一種輕量級的數(shù)據(jù)交換格式。它基于 ECMAScript (w3c制定的js規(guī)范)的一個子集, JSON采用與編程語言無關(guān)的文本格式,但是也使用了類C語言(包括C, C++, C#, Java, JavaScript, Perl, Python等)的習慣,簡潔和清晰的層次結(jié)構(gòu)使得 JSON 成為理想的數(shù)據(jù)交換語言。
? ? · 優(yōu)點
? ? ? ? · 前后兼容性高
? ? ? ? · 數(shù)據(jù)格式比較簡單,易于讀寫
? ? ? ? · 序列化后數(shù)據(jù)較小,可擴展性好,兼容性好
? ? ? ? · 與XML相比,其協(xié)議比較簡單,解析速度比較快
? ? · 缺點
? ? ? ? · 數(shù)據(jù)的描述性比XML差
? ? ? ? · 不適合性能要求為ms級別的情況
? ? ? ? · 額外空間開銷比較大
? ? · 適用場景(可替代XML)
? ? ? ? · 跨防火墻訪問
? ? ? ? · 可調(diào)式性要求高的情況
? ? ? ? · 基于Web browser的Ajax請求
? ? ? ? · 傳輸數(shù)據(jù)量相對小,實時性要求相對低(例如秒級別)的服務(wù)
(4)Fastjson
? ? · 定義
????????Fastjson是一個Java語言編寫的高性能功能完善的JSON庫。它采用一種“假定有序快速匹配”的算法,把JSON Parse的性能提升到極致。
? ? · 優(yōu)點
? ? ? ? · 接口簡單易用
? ? ? ? · 目前java語言中最快的json庫
? ? · 缺點
? ? ? ? · 過于注重快,而偏離了“標準”及功能性
? ? ? ? · 代碼質(zhì)量不高,文檔不全
? ? · 適用場景
? ? ? ? · 協(xié)議交互
? ? ? ? · Web輸出
? ? ? ? · Android客戶端
(5)Thrift
? ? · 定義:
????????Thrift并不僅僅是序列化協(xié)議,而是一個RPC框架。它可以讓你選擇客戶端與服務(wù)端之間傳輸通信協(xié)議的類別,即文本(text)和二進制(binary)傳輸協(xié)議, 為節(jié)約帶寬,提供傳輸效率,一般情況下使用二進制類型的傳輸協(xié)議。
? ? · 優(yōu)點
? ? ? ? · 序列化后的體積小,速度快
? ? ? ? · 支持多種語言和豐富的數(shù)據(jù)類型
? ? ? ? · 對于數(shù)據(jù)字段的增刪具有較強的兼容性
? ? ? ? · 支持二進制壓縮編碼
? ? · 缺點
? ? ? ? · 使用者較少
? ? ? ? · 跨防火墻訪問時,不安全
? ? ? ? · 不具有可讀性,調(diào)試代碼時相對困難
? ? ? ? · 不能與其他傳輸層協(xié)議共同使用(例如HTTP)
? ? ? ? · 無法支持向持久層直接讀寫數(shù)據(jù),即不適合做數(shù)據(jù)持久化序列化協(xié)議
? ? · 適用場景
? ? ? ? · 分布式系統(tǒng)的RPC解決方案
(6)Avro
? ? · 定義:
????????Avro屬于Apache Hadoop的一個子項目。 Avro提供兩種序列化格式:JSON格式或者Binary格式。Binary格式在空間開銷和解析性能方面可以和Protobuf媲美,Avro的產(chǎn)生解決了JSON的冗長和沒有IDL的問題
? ? · 優(yōu)點
? ? ? ? · 支持豐富的數(shù)據(jù)類型
? ? ? ? · 簡單的動態(tài)語言結(jié)合功能
? ? ? ? · 具有自我描述屬性
? ? ? ? · 提高了數(shù)據(jù)解析速度
? ? ? ? · 快速可壓縮的二進制數(shù)據(jù)形式
? ? ? ? · 可以實現(xiàn)遠程過程調(diào)用RPC
? ? ? ? · 支持跨編程語言實現(xiàn)
? ? · 缺點
? ? ? ? · 對于習慣于靜態(tài)類型語言的用戶不直觀
? ? · 適用場景
????????在Hadoop中做Hive、Pig和MapReduce的持久化數(shù)據(jù)格式
(7)Protobuf
? ? · 定義
????????protocol buffers由谷歌開源而來,在谷歌內(nèi)部久經(jīng)考驗。它將數(shù)據(jù)結(jié)構(gòu)以.proto文件進行描述,通過代碼生成工具可以生成對應(yīng)數(shù)據(jù)結(jié)構(gòu)的POJO對象和Protobuf相關(guān)的方法和屬性。
? ? · 優(yōu)點
? ? ? ? · 序列化后碼流小,性能高
? ? ? ? · 結(jié)構(gòu)化數(shù)據(jù)存儲格式(XML JSON等)
? ? ? ? · 通過標識字段的順序,可以實現(xiàn)協(xié)議的前向兼容
? ? ? ? · 結(jié)構(gòu)化的文檔更容易管理和維護
? ? · 缺點
? ? ? ? · 需要依賴于工具生成代碼
? ? ? ? · 支持的語言相對較少,官方只支持Java、C++ 、Python
? ? · 適用場景
? ? ? ? · 對性能要求高的RPC調(diào)用
? ? ? ? · 具有良好的跨防火墻的訪問屬性
? ? ? ? · 適合應(yīng)用層對象的持久化
(8)其它
? ? · protostuff基于protobuf協(xié)議,但不需要配置proto文件,直接導(dǎo)包即
? ? · Jboss marshaling可以直接序列化java類, 無須實java.io.Serializable接口
? ? · Message pack一個高效的二進制序列化格式
? ? · Hessian采用二進制協(xié)議的輕量級remoting onhttp工具
? ? · kryo基于protobuf協(xié)議,只支持java語言,需要注冊(Registration),然后序列化(Output),反序列化(Input)
7.2.5 Netty代碼
7.2.5.1 3.3.0版本
· 依賴
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId>
<version>3.3.0.Final</version>
</dependency>
· 服務(wù)端
class ServerHandler extends SimpleChannelHandler {
/**
* 通道關(guān)閉的時候觸發(fā)
*/
@Override
????public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
????System.out.println("channelClosed");
????}
/**
* 必須是連接已經(jīng)建立,關(guān)閉通道的時候才會觸發(fā).
*/
????@Override
????public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
????super.channelDisconnected(ctx, e);
????System.out.println("channelDisconnected");
????}
/**
* 捕獲異常
*/
@Override
????public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
????super.exceptionCaught(ctx, e);
????System.out.println("exceptionCaught");
????}
/**
* 接受消息
*/
????public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
????super.messageReceived(ctx, e);
????// System.out.println("messageReceived");
????System.out.println("服務(wù)器端收到客戶端消息:"+e.getMessage());
????//回復(fù)內(nèi)容
????ctx.getChannel().write("好的");
????}
}
// netty 服務(wù)器端
public class NettyServer {
????public static void main(String[] args) {
????????// 創(chuàng)建服務(wù)類對象
????????ServerBootstrap serverBootstrap = new ServerBootstrap();
????????// 創(chuàng)建兩個線程池 分別為監(jiān)聽監(jiān)聽端口 ,nio監(jiān)聽
????????ExecutorService boos = Executors.newCachedThreadPool();
????????ExecutorService worker = Executors.newCachedThreadPool();
????????// 設(shè)置工程 并把兩個線程池加入中
????????serverBootstrap.setFactory(new NioServerSocketChannelFactory(boos, worker));
????????// 設(shè)置管道工廠
????????serverBootstrap.setPipelineFactory(new ChannelPipelineFactory() {
????????????public ChannelPipeline getPipeline() throws Exception {
????????????ChannelPipeline pipeline = Channels.pipeline();
????????????//將數(shù)據(jù)轉(zhuǎn)換為string類型.
????????????pipeline.addLast("decoder", new StringDecoder());
????????????pipeline.addLast("encoder", new StringEncoder());
????????????pipeline.addLast("serverHandler", new ServerHandler());
????????????return pipeline;
????????}
????});
????// 綁定端口號
????serverBootstrap.bind(new InetSocketAddress(9090));
????System.out.println("netty server啟動....");
????}
}
· 客戶端
class ClientHandler extends SimpleChannelHandler {
/**
* 通道關(guān)閉的時候觸發(fā)
*/
????@Override
????public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
????????System.out.println("channelClosed");
????}
/**
* 必須是連接已經(jīng)建立,關(guān)閉通道的時候才會觸發(fā).
*/
@Override
????public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
????super.channelDisconnected(ctx, e);
????System.out.println("channelDisconnected");
????}
/**
* 捕獲異常
*/
@Override
????public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws ????Exception {
????????super.exceptionCaught(ctx, e);
????????System.out.println("exceptionCaught");
????}
/**
* 接受消息
*/
????public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
????super.messageReceived(ctx, e);
????// System.out.println("messageReceived");
????System.out.println("服務(wù)器端向客戶端回復(fù)內(nèi)容:"+e.getMessage());
????//回復(fù)內(nèi)容
????// ctx.getChannel().write("好的");
????}
}
// Netty客戶端
public class NettyClient {
????public static void main(String[] args) {
????????System.out.println("netty client啟動...");
????????// 創(chuàng)建客戶端類
????????ClientBootstrap clientBootstrap = new ClientBootstrap();
????????// 線程池
????????ExecutorService boos = Executors.newCachedThreadPool();
????????ExecutorService worker = Executors.newCachedThreadPool();
????????clientBootstrap.setFactory(new NioClientSocketChannelFactory(boos, worker));
????????clientBootstrap.setPipelineFactory(new ChannelPipelineFactory() {
????????????public ChannelPipeline getPipeline() throws Exception {
????????????????ChannelPipeline pipeline = Channels.pipeline();
????????????????// 將數(shù)據(jù)轉(zhuǎn)換為string類型.
????????????????pipeline.addLast("decoder", new StringDecoder());
????????????????pipeline.addLast("encoder", new StringEncoder());
????????????????pipeline.addLast("clientHandler", new ClientHandler());
????????????????return pipeline;
????????????}
????????});
????????//連接服務(wù)端
????????ChannelFuture connect = clientBootstrap.connect(new ????InetSocketAddress("127.0.0.1", 9090));
????????Channel channel = connect.getChannel();
????????System.out.println("client start");
????????Scanner scanner= new Scanner(System.in);
????????while (true) {
????????????System.out.println("請輸輸入內(nèi)容...");
????????????channel.write(scanner.next());
????????}
????}
}
7.2.5.2 5.0版本
· 依賴
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
????<groupId>io.netty</groupId>
????<artifactId>netty-all</artifactId>
????<version>5.0.0.Alpha2</version>
</dependency>
· 服務(wù)端
class ServerHandler extends ChannelHandlerAdapter {
/**
* 當通道被調(diào)用,執(zhí)行該方法
*/
????@Override
????public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
????// 接收數(shù)據(jù)
????String value = (String) msg;
????System.out.println("Server msg:" + value);
????// 回復(fù)給客戶端 “您好!”
????String res = "好的...";
????ctx.writeAndFlush(Unpooled.copiedBuffer(res.getBytes()));
????}
}
// Netty服務(wù)端
public class NettyServer {
????public static void main(String[] args) throws InterruptedException {
????????System.out.println("服務(wù)器端已經(jīng)啟動....");
????????// 1.創(chuàng)建2個線程,一個負責接收客戶端連接, 一個負責進行 傳輸數(shù)據(jù)
????????NioEventLoopGroup pGroup = new NioEventLoopGroup();
????????NioEventLoopGroup cGroup = new NioEventLoopGroup();
????????// 2. 創(chuàng)建服務(wù)器輔助類
????????ServerBootstrap b = new ServerBootstrap();
????????b.group(pGroup, cGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024)
????????// 3.設(shè)置緩沖區(qū)與發(fā)送區(qū)大小
????????.option(ChannelOption.SO_SNDBUF, 32 * 1024).option(ChannelOption.SO_RCVBUF, 32 * 1024)
????????.childHandler(new ChannelInitializer<SocketChannel>() {
????????????@Override
????????????protected void initChannel(SocketChannel sc) throws Exception {
????????????????sc.pipeline().addLast(new StringDecoder());
????????????????sc.pipeline().addLast(new ServerHandler());
????????????}
????????});
????????ChannelFuture cf = b.bind(8080).sync();
????????cf.channel().closeFuture().sync();
????????pGroup.shutdownGracefully();
????????cGroup.shutdownGracefully();
????}
}
· 客戶端
class ClientHandler extends ChannelHandlerAdapter {
/**
* 當通道被調(diào)用,執(zhí)行該方法
*/
????@Override
????public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
????????// 接收數(shù)據(jù)
????????String value = (String) msg;
????????System.out.println("client msg:" + value);
????}
}
public class NettyClient {
????public static void main(String[] args) throws InterruptedException {
????????System.out.println("客戶端已經(jīng)啟動....");
????????// 創(chuàng)建負責接收客戶端連接
????????NioEventLoopGroup pGroup = new NioEventLoopGroup();
????????Bootstrap b = new Bootstrap();
????????b.group(pGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
????????????@Override
????????????protected void initChannel(SocketChannel sc) throws Exception {
????????????sc.pipeline().addLast(new StringDecoder());
????????????sc.pipeline().addLast(new ClientHandler());
????????}
????});
????ChannelFuture cf = b.connect("127.0.0.1", 8080).sync();
????cf.channel().writeAndFlush(Unpooled.wrappedBuffer("itmayiedu".getBytes()));
????cf.channel().writeAndFlush(Unpooled.wrappedBuffer("itmayiedu".getBytes()));
????// 等待客戶端端口號關(guān)閉
????cf.channel().closeFuture().sync();
????pGroup.shutdownGracefully();
????}
}
7.3 SPI
7.3.1 Java SPI
7.3.1.1 概述
(1)概念
????SPI全稱(service provider interface),是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機制。目前市面上有很多框架都是用它來做服務(wù)的擴展發(fā)現(xiàn),大家耳熟能詳?shù)娜鏙DBC、日志框架都有用到;
? ??簡單來說,它是一種動態(tài)替換發(fā)現(xiàn)的機制。
(2)舉個簡單的例子,
? ? · 如果我們定義了一個規(guī)范,需要第三方廠商去實現(xiàn),
? ? · 那么對于我們應(yīng)用方來說,只需要集成對應(yīng)廠商的插件,既可以完成對應(yīng)規(guī)范的實現(xiàn)機制。
? ? · 形成一種插拔式的擴展手段。
7.3.1.2 SPI規(guī)范
(1)需要在classpath下創(chuàng)建一個目錄,該目錄命名必須是:META-INF/services
(2)在該目錄下創(chuàng)建一個properties文件,該文件需要滿足以下幾個條件
? ? ? ? · 文件名必須是擴展的接口的全路徑名稱
? ? ? ? · 文件內(nèi)部描述的是該擴展接口的所有實現(xiàn)類
? ? ? ? · 文件的編碼格式是UTF-8
(3)通過java.util.ServiceLoader的加載機制來發(fā)現(xiàn)
7.3.1.3 SPI實例
????JDK官方提供了java.sql.Driver這個驅(qū)動擴展點,但是你們并沒有看到JDK中有對應(yīng)的Driver實現(xiàn)。
????以連接Mysql為例,我們需要添加mysql-connector-java依賴。你們可以在這個jar包中找到SPI的配置信息。所以java.sql.Driver由各個數(shù)據(jù)庫廠商自行實現(xiàn)。
7.3.1.4 SPI的缺點
(1)JDK標準的SPI會一次性加載實例化擴展點的所有實現(xiàn)
????????就是如果你在META-INF/service下的文件里面加了N個實現(xiàn)類,那么JDK啟動的時候都會一次性全部加載。
????????那么如果有的擴展點實現(xiàn)初始化很耗時或者如果有些實現(xiàn)類并沒有用到,那么會很浪費資源
(2)如果擴展點加載失敗,會導(dǎo)致調(diào)用方報錯,而且這個錯誤很難定位到是這個原因
7.3.2 Dubbo SPI
(1)Dubbo擴展的新特性
? ? · 內(nèi)嵌在dubbo中
? ? · 支持通過SPI文件聲明擴展實現(xiàn)(interfce必須有@SPI注解),
格式為extensionName=extensionClassName,extensionName類似于spring的beanName
? ? · 支持通過配置指定extensionName來從SPI文件中選出對應(yīng)實現(xiàn)
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("defineProtocol")
? ? 通過配置文件中的權(quán)限定名,加載實現(xiàn)類。在運行時,可以動態(tài)為接口替換實現(xiàn)類。
(2)源碼分析
????ServiceConfig類中的一行代碼:
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class). getAdaptiveExtension();
· Protocol
????一個是在類級別上的@SPI(“dubbo”),@SPI?表示當前這個接口是一個擴展點,可以實現(xiàn)自己的擴展實現(xiàn),默認的擴展點是DubboProtocol。
????另一個是@Adaptive,表示一個自適應(yīng)擴展點,在方法級別上,會動態(tài)生成一個適配器類。
@SPI("dubbo")
publicinterfaceProtocol{
/**
? ? * 獲取缺省端口,當用戶沒有配置端口時使用。
? ? *
*@return缺省端口
? ? */
intgetDefaultPort();
/**
? ? * 暴露遠程服務(wù):<br>
? ? * 1. 協(xié)議在接收請求時,應(yīng)記錄請求來源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
? ? * 2. export()必須是冪等的,也就是暴露同一個URL的Invoker兩次,和暴露一次沒有區(qū)別。<br>
? ? * 3. export()傳入的Invoker由框架實現(xiàn)并傳入,協(xié)議不需要關(guān)心。<br>
? ? *
*@param? ? 服務(wù)的類型
*@paraminvoker 服務(wù)的執(zhí)行體
*@returnexporter 暴露服務(wù)的引用,用于取消暴露
*@throwsRpcException 當暴露服務(wù)出錯時拋出,比如端口已占用
? ? */
@Adaptive
Exporterexport(Invoker<T> invoker)throwsRpcException;
/**
? ? * 引用遠程服務(wù):<br>
? ? * 1. 當用戶調(diào)用refer()所返回的Invoker對象的invoke()方法時,協(xié)議需相應(yīng)執(zhí)行同URL遠端export()傳入的Invoker對象的invoke()方法。<br>
? ? * 2. refer()返回的Invoker由協(xié)議實現(xiàn),協(xié)議通常需要在此Invoker中發(fā)送遠程請求。<br>
? ? * 3. 當url中有設(shè)置check=false時,連接失敗不能拋出異常,并內(nèi)部自動恢復(fù)。<br>
? ? *
*@param? 服務(wù)的類型
*@paramtype 服務(wù)的類型
*@paramurl? 遠程服務(wù)的URL地址
*@returninvoker 服務(wù)的本地代理
*@throwsRpcException 當連接服務(wù)提供方失敗時拋出
? ? */
@Adaptive
Invokerrefer(Class<T> type, URL url)throwsRpcException;
/**
? ? * 釋放協(xié)議:<br>
? ? * 1. 取消該協(xié)議所有已經(jīng)暴露和引用的服務(wù)。<br>
? ? * 2. 釋放協(xié)議所占用的所有資源,比如連接和端口。<br>
? ? * 3. 協(xié)議在釋放后,依然能暴露和引用新的服務(wù)。<br>
? ? */
voiddestroy();
}
· 方法調(diào)用
8 Dubbo源碼分析
8.1 框架設(shè)計?
? ? 總體分為Business、RPC和Remoting三層設(shè)計
· Service服務(wù)接口層:該層是與實際業(yè)務(wù)邏輯相關(guān)的,根據(jù)服務(wù)提供方和服務(wù)消費方的業(yè)務(wù)設(shè)計對應(yīng)的接口和實現(xiàn)。
· config配置層:對外配置接口,以 ServiceConfig, ReferenceConfig 為中心,可以直接初始化配置類,也可以通過 spring 解析配置生成配置類
· proxy服務(wù)代理層:服務(wù)接口透明代理,生成服務(wù)的客戶端 Stub 和服務(wù)器端 Skeleton, 以 ServiceProxy 為中心,擴展接口為 ProxyFactory
· registry注冊中心層:封裝服務(wù)地址的注冊與發(fā)現(xiàn),以服務(wù) URL 為中心,擴展接口為 RegistryFactory, Registry, RegistryService
· cluster路由層:封裝多個提供者的路由及負載均衡,并橋接注冊中心,以 Invoker 為中心,擴展接口為 Cluster, Directory, Router, LoadBalance
· monitor監(jiān)控層:RPC 調(diào)用次數(shù)和調(diào)用時間監(jiān)控,以 Statistics 為中心,擴展接口為 MonitorFactory, Monitor, MonitorService
· protocol遠程調(diào)用層:封裝 RPC 調(diào)用,以 Invocation, Result 為中心,擴展接口為 Protocol, Invoker, Exporter
· 信息交換層:封裝請求響應(yīng)模式,同步轉(zhuǎn)異步,以 Request, Response 為中心,擴展接口為 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
· 網(wǎng)絡(luò)傳輸層:抽象 mina 和 netty 為統(tǒng)一接口,以 Message 為中心,擴展接口為 Channel, Transporter, Client, Server, Codec
· serialize數(shù)據(jù)序列化層:可復(fù)用的一些工具,擴展接口為 Serialization, ObjectInput, ObjectOutput, ThreadPool
8.2 dubbo原理-啟動解析、加載配置信息
(1)解析XML
????在dubboNamespaceHandler中將<dubbo:provider>等解析為對應(yīng)的xxxConfig對象.
(2)標簽內(nèi)容解析,封裝為beanDefinition對象
8.3 dubbo原理 -服務(wù)暴露
? ? Invoker是用戶接口的代理對象實例,會經(jīng)過層層包裝。
????在使用Protocol時,會調(diào)用兩個protocol。一個是DubboProtocol,其對應(yīng)的DubboExporter打開Nttey服務(wù)器,監(jiān)聽對應(yīng)的服務(wù)提供者端口;一個是RegistryProtocol,其對應(yīng)的RegistryExporter對將提供者地址(服務(wù)器地址,如http://127.0.0.1:20880)作為key,實例化的Invoker(具體服務(wù)接口的實現(xiàn)serviceImpl)作為value添加到注冊表中,并向Zookeeper注冊中心注冊服務(wù)(添加節(jié)點信息)。
8.4 dubbo原理 -服務(wù)引用
????首先ReferenceConfig類的init方法調(diào)用Protocol的refer方法生成Invoker實例,這是服務(wù)消費的關(guān)鍵。接下來把Invoker轉(zhuǎn)換為客戶端需要的接口(如:HelloWorld)。
8.5 dubbo原理 -服務(wù)調(diào)用
? ? 集群容錯模式選擇:
9 Dubbox
10 基于Spring的Dubbo整合
10.1 依賴
<dependency>
????<groupId>com.alibaba</groupId>
????<artifactId>dubbo</artifactId>
????<version>2.5.6</version>
</dependency>
<dependency>
????<groupId>com.github.sgroschupf</groupId>
????<artifactId>zkclient</artifactId>
????<version>0.1</version>
</dependency>
10.2 Provider
10.2.1 配置文件provider.xml
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans? ? ? ? ? http://www.springframework.org/schema/beans/spring-beans.xsd? ? ? ? ? http://code.alibabatech.com/schema/dubbo? ? ? ? ? http://code.alibabatech.com/schema/dubbo/dubbo.xsd ">
<!-- 提供方應(yīng)用信息,用于計算依賴關(guān)系 -->
<dubbo:application name="provider" />
<!-- 使用zookeeper注冊中心暴露服務(wù)地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
<!-- 用dubbo協(xié)議在29014端口暴露服務(wù) -->
<dubbo:protocol name="dubbo" port="29014" />
<!-- 聲明需要暴露的服務(wù)接口 -->
<dubbo:service interface="com.itmayiedu.service.UserService"
ref="orderService" />
<!-- 具體的實現(xiàn)bean -->
<bean id="orderService" class="com.itmayiedu.service.impl.UserServiceImpl" />
</beans>
10.2.2 Service
public class UserServiceImpl implements UserService {
????public String getList(Integer id) {
????????System.out.println("客戶端有人來消費了....");
????????if (id==1) {
????????????return "我";
????????}
????????if (id==2) {
????????????return "扎克伯格";
????????}
????????if (id==3) {
????????????return "馬化騰";
????????}
????????return "沒有找到";
????}
}
10.2.3 啟動
public class TestMember {
????public static void main(String[] args) throws IOException {
????// 發(fā)布服務(wù)
????????ClassPathXmlApplicationContext app = new ????????ClassPathXmlApplicationContext("provider.xml");
????????app.start();// 加載
????????System.out.println("服務(wù)發(fā)布成功...");
????????System.in.read(); // 讓程序阻塞
????}
}
10.3 Consumer
10.3.1 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- 消費方應(yīng)用名,用于計算依賴關(guān)系,不是匹配條件,不要與提供方一樣 -->
<dubbo:application name="consumer" />
<!-- 使用multicast廣播注冊中心暴露發(fā)現(xiàn)服務(wù)地址 -->
<dubbo:registry protocol="zookeeper" address="zookeeper://127.0.0.1:2181" />
<!-- 生成遠程服務(wù)代理,可以和本地bean一樣使用demoService -->
<dubbo:reference id="userService" interface="com.itmayiedu.service.UserService" />
</beans>
10.3.2 啟動
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("consumer.xml");
UserService userService = (UserService) app.getBean("userService");
String name = userService.getList(1);
System.out.println("name:" + name);