Dubbo

目錄
? ? 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)演變

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)

圖 2-1 RPC結(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 工作原理

圖 2-2 RPC工作原理
圖 2-3 RPC工作步驟

(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)

圖 3-1 Dubbo架構(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 配置原則

圖 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的拆包和封包問題。

圖 補充 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-1 SPI實例

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)用

圖 7-2 方法調(diào)用圖

8 Dubbo源碼分析

8.1 框架設(shè)計?

? ? 總體分為Business、RPC和Remoting三層設(shè)計

圖 8-1 框架設(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對象.

圖 8-2 啟動解析

(2)標簽內(nèi)容解析,封裝為beanDefinition對象

圖 8-3 封裝BeanDefinition對象

8.3 dubbo原理 -服務(wù)暴露

圖 8-3 服務(wù)暴露流程
圖 8-4 服務(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 服務(wù)引用

8.5 dubbo原理 -服務(wù)調(diào)用

圖 8-6 服務(wù)調(diào)用

? ? 集群容錯模式選擇:

圖 8-7 服務(wù)調(diào)用2

9 Dubbox

圖 9-1 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);

11 常見面試題總結(jié)

dubbo的發(fā)展由來 - 知乎

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容