目錄
? ? 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àn)面試題總結(jié)參考目錄
? ? · 尚硅谷Dubbo
? ? · CSDN
1 應(yīng)用架構(gòu)演變
1.1 單一應(yīng)用架構(gòu)
????當(dāng)網(wǎng)站流量很小時(shí)划提,只需一個(gè)應(yīng)用,將所有功能都部署在一起,以減少部署節(jié)點(diǎn)和成本拇派。?此時(shí),用于簡(jiǎn)化增刪改查工作量的數(shù)據(jù)訪問(wèn)框架(ORM) 是關(guān)鍵。
1.2 垂直應(yīng)用架構(gòu)
????當(dāng)訪問(wèn)量逐漸增大,單一應(yīng)用增加機(jī)器帶來(lái)的加速度越來(lái)越小晕翠,將應(yīng)用拆成互不相干的幾個(gè)應(yīng)用,以提升效率砍濒。?此時(shí)淋肾,用于加速前端頁(yè)面開(kāi)發(fā)的Web框架(MVC) 是關(guān)鍵。
1.3 分布式服務(wù)架構(gòu)
????當(dāng)垂直應(yīng)用越來(lái)越多爸邢,應(yīng)用之間交互不可避免樊卓,將核心業(yè)務(wù)抽取出來(lái),作為獨(dú)立的服務(wù)杠河,逐漸形成穩(wěn)定的服務(wù)中心简识,使前端應(yīng)用能更快速的響應(yīng)多變的市場(chǎng)需求。?此時(shí)感猛,用于提高業(yè)務(wù)復(fù)用及整合的分布式服務(wù)框架(RPC) 是關(guān)鍵。
1.4 流動(dòng)計(jì)算架構(gòu)
????當(dāng)服務(wù)越來(lái)越多奢赂,容量的評(píng)估陪白,小服務(wù)資源的浪費(fèi)等問(wèn)題逐漸顯現(xiàn),此時(shí)需增加一個(gè)調(diào)度中心基于訪問(wèn)壓力實(shí)時(shí)管理集群容量膳灶,提高集群利用率咱士。?此時(shí)立由,用于提高機(jī)器利用率的資源調(diào)度和治理中心(SOA) 是關(guān)鍵。
2 RPC
2.1 定義
? ??RPC【Remote Procedure Call】是指遠(yuǎn)程過(guò)程調(diào)用序厉,是一種進(jìn)程間通信方式锐膜,他是一種技術(shù)的思想,而不是規(guī)范弛房。它允許程序調(diào)用另一個(gè)地址空間(通常是共享網(wǎng)絡(luò)的另一臺(tái)機(jī)器上)的過(guò)程或函數(shù)道盏,而不用程序員顯式編碼這個(gè)遠(yuǎn)程調(diào)用的細(xì)節(jié)。
? ? 另外文捶,總結(jié):詳解RMI與RPC的區(qū)別 - 知乎
2.2 結(jié)構(gòu)
????RPC 服務(wù)方通過(guò) RpcServer 去導(dǎo)出(export)遠(yuǎn)程接口方法荷逞,而客戶(hù)方通過(guò) RpcClient 去引入(import)遠(yuǎn)程接口方法〈馀牛客戶(hù)方像調(diào)用本地方法一樣去調(diào)用遠(yuǎn)程接口方法种远,RPC 框架提供接口的代理實(shí)現(xiàn),實(shí)際的調(diào)用將委托給代理RpcProxy 顽耳。代理封裝調(diào)用信息并將調(diào)用轉(zhuǎn)交給RpcInvoker 去實(shí)際執(zhí)行坠敷。在客戶(hù)端的RpcInvoker 通過(guò)連接器RpcConnector 去維持與服務(wù)端的通道RpcChannel,并使用RpcProtocol 執(zhí)行協(xié)議編碼(encode)并將編碼后的請(qǐng)求消息通過(guò)通道發(fā)送給服務(wù)方射富。
? ? 具體功能如下:
(1)RpcServer? 負(fù)責(zé)導(dǎo)出(export)遠(yuǎn)程接口?
(2)RpcClient? ?負(fù)責(zé)導(dǎo)入(import)遠(yuǎn)程接口的代理實(shí)現(xiàn)?
(3)RpcProxy? ?遠(yuǎn)程接口的代理實(shí)現(xiàn)?
(4)RpcInvoker?
? ???????? 客戶(hù)方實(shí)現(xiàn):負(fù)責(zé)編碼調(diào)用信息和發(fā)送調(diào)用請(qǐng)求到服務(wù)方并等待調(diào)用結(jié)果返回?
? ???????? 服務(wù)方實(shí)現(xiàn):負(fù)責(zé)調(diào)用服務(wù)端接口的具體實(shí)現(xiàn)并返回調(diào)用結(jié)果?
(5)RpcProtocol? ? ? 負(fù)責(zé)協(xié)議編/解碼?
(6)RpcConnector? 負(fù)責(zé)維持客戶(hù)方和服務(wù)方的連接通道和發(fā)送數(shù)據(jù)到服務(wù)方?
(7)RpcAcceptor? ? ?負(fù)責(zé)接收客戶(hù)方請(qǐng)求并返回請(qǐng)求結(jié)果?
(8)RpcProcessor? ?負(fù)責(zé)在服務(wù)方控制調(diào)用過(guò)程膝迎,包括管理調(diào)用線(xiàn)程池、超時(shí)時(shí)間等?
(9)RpcChannel? ? ? 數(shù)據(jù)傳輸通道
2.3 工作原理
(1)Client像調(diào)用本地服務(wù)似的調(diào)用遠(yuǎn)程服務(wù)辉浦;
(2)Client stub接收到調(diào)用后弄抬,將方法、參數(shù)序列化
(3)客戶(hù)端通過(guò)sockets將消息發(fā)送到服務(wù)端
(4)Server stub 收到消息后進(jìn)行解碼(將消息對(duì)象反序列化)
(5)Server stub 根據(jù)解碼結(jié)果調(diào)用本地的服務(wù)
(6)本地服務(wù)執(zhí)行(對(duì)于服務(wù)端來(lái)說(shuō)是本地執(zhí)行)并將結(jié)果返回給Server stub
(7)Server stub將返回結(jié)果打包成消息(將結(jié)果消息對(duì)象序列化)
(8)服務(wù)端通過(guò)sockets將消息發(fā)送到客戶(hù)端
(9)Client stub接收到結(jié)果消息宪郊,并進(jìn)行解碼(將結(jié)果消息發(fā)序列化)
(10)客戶(hù)端得到最終結(jié)果掂恕。
· 關(guān)于對(duì)stub的理解
????????stub:RPC(Remote Procedure Call protocol)的一個(gè)重要思想就是使遠(yuǎn)程調(diào)用看起來(lái)象當(dāng)?shù)氐恼{(diào)用一樣,也就是說(shuō)調(diào)用進(jìn)程無(wú)需知道被調(diào)進(jìn)程具體在哪臺(tái)機(jī)器上執(zhí)行弛槐。Stub就是用來(lái)保證此特性的很重要的部分懊亡。具體的講,比如在客戶(hù)端乎串,一個(gè)進(jìn)程在執(zhí)行過(guò)程中調(diào)用到了某個(gè)函數(shù)fn()店枣,此函數(shù)的具體實(shí)現(xiàn)是在遠(yuǎn)程的某臺(tái)機(jī)器上,那么此進(jìn)程實(shí)際上是調(diào)用了位于當(dāng)?shù)貦C(jī)器上的另外一個(gè)版本的fn()(起名為c_fn())叹誉,此c_fn()就是客戶(hù)端的一個(gè)stub. 對(duì)應(yīng)的鸯两,當(dāng)客戶(hù)端的消息發(fā)送到服務(wù)器端時(shí),服務(wù)器端也不是把消息直接就交給真正的fn(),而是同樣先交給一個(gè)不同版本的fn()(起名為s_fn())长豁,此s_fn()就是服務(wù)器端的一個(gè)stub.?
2.4 工作方式
(1)同步調(diào)用:客戶(hù)方等待調(diào)用執(zhí)行完成并返回結(jié)果钧唐。
(2)異步調(diào)用:客戶(hù)方調(diào)用后不用等待執(zhí)行結(jié)果返回,但依然可以通過(guò)回調(diào)通知等方式獲取返回結(jié)果匠襟。若客戶(hù)方不關(guān)心調(diào)用返回結(jié)果钝侠,則變成單向異步調(diào)用该园,單向調(diào)用不用返回結(jié)果。
3 Dubbo概述
3.1 概念
? ??Dubbo是一個(gè)分布式服務(wù)框架帅韧,致力于提供高性能和透明化的RPC遠(yuǎn)程服務(wù)調(diào)用方案里初,SOA服務(wù)治理方案。
????簡(jiǎn)單的說(shuō)忽舟,dubbo就是個(gè)服務(wù)框架双妨,如果沒(méi)有分布式的需求,其實(shí)是不需要用的萧诫,只有在分布式的時(shí)候斥难,才有dubbo這樣的分布式服務(wù)框架的需求,并且本質(zhì)上是個(gè)服務(wù)調(diào)用帘饶,說(shuō)白了就是個(gè)遠(yuǎn)程服務(wù)調(diào)用的分布式框架哑诊。
3.2 三大核心能力
(1)遠(yuǎn)程通訊,提供對(duì)多種基于長(zhǎng)連接的NiO框架抽象封裝及刻,包括多種線(xiàn)程模型镀裤,序列化,以及“請(qǐng)求一響應(yīng)”模式的信息交換方式缴饭。
(2)集群容錯(cuò): 提供基于接口方法的透明遠(yuǎn)程過(guò)程調(diào)用暑劝,包括多協(xié)議支持。以及負(fù)載均衡颗搂,失敗容錯(cuò)担猛,地址路由,動(dòng)態(tài)配置等集群支持丢氢。
(3)自動(dòng)發(fā)現(xiàn):基于注冊(cè)中心目錄服務(wù)傅联,使用服務(wù)消費(fèi)能動(dòng)態(tài)查找服務(wù)提供方,使地址透明,使用服務(wù)提供方可以平滑增加或減少服務(wù)器
3.3 架構(gòu)
(1)服務(wù)提供者(Provider):暴露服務(wù)的服務(wù)提供方,服務(wù)提供者在啟動(dòng)時(shí)疚察,向注冊(cè)中心注冊(cè)自己提供的服務(wù)蒸走。
(2)服務(wù)消費(fèi)者(Consumer):調(diào)用遠(yuǎn)程服務(wù)的服務(wù)消費(fèi)方,服務(wù)消費(fèi)者在啟動(dòng)時(shí)貌嫡,向注冊(cè)中心訂閱自己所需的服務(wù)篙程,服務(wù)消費(fèi)者缕粹,從提供者地址列表中嫁佳,基于軟負(fù)載均衡算法月培,選一臺(tái)提供者進(jìn)行調(diào)用,如果調(diào)用失敗夫椭,再選另一臺(tái)調(diào)用步咪。
(3)注冊(cè)中心(Registry):注冊(cè)中心返回服務(wù)提供者地址列表給消費(fèi)者,如果有變更益楼,注冊(cè)中心將基于長(zhǎng)連接推送變更數(shù)據(jù)給消費(fèi)者
(4)監(jiān)控中心(Monitor):服務(wù)消費(fèi)者和提供者猾漫,在內(nèi)存中累計(jì)調(diào)用次數(shù)和調(diào)用時(shí)間,定時(shí)每分鐘發(fā)送一次統(tǒng)計(jì)數(shù)據(jù)到監(jiān)控中心
3.4 調(diào)用關(guān)系
(1)服務(wù)容器負(fù)責(zé)啟動(dòng)感凤,加載悯周,運(yùn)行服務(wù)提供者。
(2)服務(wù)提供者在啟動(dòng)時(shí)陪竿,向注冊(cè)中心注冊(cè)自己提供的服務(wù)禽翼。
(3)服務(wù)消費(fèi)者在啟動(dòng)時(shí),向注冊(cè)中心訂閱自己所需的服務(wù)族跛。
(4)注冊(cè)中心返回服務(wù)提供者地址列表給消費(fèi)者闰挡,如果有變更,注冊(cè)中心將基于長(zhǎng)連接推送變更數(shù)據(jù)給消費(fèi)者礁哄。
(5)服務(wù)消費(fèi)者长酗,從提供者地址列表中,基于軟負(fù)載均衡算法桐绒,選一臺(tái)提供者進(jìn)行調(diào)用夺脾,如果調(diào)用失敗,再選另一臺(tái)調(diào)用茉继。
(6)服務(wù)消費(fèi)者和提供者咧叭,在內(nèi)存中累計(jì)調(diào)用次數(shù)和調(diào)用時(shí)間,定時(shí)每分鐘發(fā)送一次統(tǒng)計(jì)數(shù)據(jù)到監(jiān)控中心烁竭。
3.5 注冊(cè)中心
(1)可選方案:zookeeper菲茬、Redis
(2)建議使用dubbo-2.3.3以上版本的使用zookeeper注冊(cè)中心客戶(hù)端
????????·Zookeeper是Apache Hadoop的子項(xiàng)目,強(qiáng)度相對(duì)較好派撕,建議生產(chǎn)環(huán)境使用該注冊(cè)中心婉弹。
????????· Dubbo未對(duì)Zookeeper服務(wù)器端做任何侵入修改,只需安裝原生的Zookeeper服務(wù)器即可腥刹, 所有注冊(cè)中心邏輯適配都在調(diào)用Zookeeper客戶(hù)端時(shí)完成
? ? ? ? · Dubbo使用ZooKeeper發(fā)布服務(wù)時(shí)马胧,使用的是ZooKeeper的持久節(jié)點(diǎn)
? ? 具體如何通過(guò)Zookeeper和Redis構(gòu)建,參考:Dubbo(一)——Dubbo及注冊(cè)中心原理_baoyu_G的博客-CSDN博客_dubbo注冊(cè)
3.6 Dubbo的特性
(1)健狀性:
? ? ? ? · 監(jiān)控中心宕掉不影響使用衔峰,只是丟失部分采樣數(shù)據(jù)
? ? ? ? · 數(shù)據(jù)庫(kù)宕掉后佩脊,注冊(cè)中心仍能通過(guò)緩存提供服務(wù)列表查詢(xún),但不能注冊(cè)新服務(wù)
? ? ? ? · 注冊(cè)中心對(duì)等集群垫卤,任意一臺(tái)宕掉后威彰,將自動(dòng)切換到另一臺(tái)
? ? ? ? ·?注冊(cè)中心全部宕掉后,服務(wù)提供者和服務(wù)消費(fèi)者仍能通過(guò)本地緩存通訊
? ? ? ? · 服務(wù)提供者無(wú)狀態(tài)穴肘,任意一臺(tái)宕掉后歇盼,不影響使用
? ? ? ? · 服務(wù)提供者全部宕掉后,服務(wù)消費(fèi)者應(yīng)用將無(wú)法使用评抚,并無(wú)限次重連等待服務(wù)提供者恢復(fù)
(2)伸縮性:
????????注冊(cè)中心為對(duì)等集群豹缀,可動(dòng)態(tài)增加機(jī)器部署實(shí)例伯复,所有客戶(hù)端將自動(dòng)發(fā)現(xiàn)新的注冊(cè)中心?
????????服務(wù)提供者無(wú)狀態(tài),可動(dòng)態(tài)增加機(jī)器部署實(shí)例邢笙,注冊(cè)中心將推送新的服務(wù)提供者信息給消費(fèi)者
(3)升級(jí)性:
????????當(dāng)服務(wù)集群規(guī)模進(jìn)一步擴(kuò)大啸如,帶動(dòng)IT治理結(jié)構(gòu)進(jìn)一步升級(jí),需要實(shí)現(xiàn)動(dòng)態(tài)部署氮惯,進(jìn)行流動(dòng)計(jì)算叮雳,現(xiàn)有分布式服務(wù)架構(gòu)不會(huì)帶來(lái)阻力。
3.7 Dubbo-admin管理平臺(tái)
(1)圖形化的服務(wù)管理頁(yè)面妇汗;安裝時(shí)需要指定注冊(cè)中心地址帘不,即可從注冊(cè)中心中獲取到所有的提供者/消費(fèi)者進(jìn)行配置管理,從而進(jìn)行服務(wù)治理杨箭。
(2)主要包含
????????·路由規(guī)則
????????·動(dòng)態(tài)配置
????????·服務(wù)降級(jí)
????????·訪問(wèn)控制
????????·權(quán)重調(diào)整 負(fù)載均衡等管理功能
4 Dubbo配置
4.1 配置原則
· JVM啟動(dòng) -D 參數(shù)優(yōu)先寞焙,這樣可以使用戶(hù)在部署和啟動(dòng)時(shí)進(jìn)行參數(shù)重寫(xiě),比如在啟動(dòng)時(shí)需改變協(xié)議的端口告唆。
· XML次之棺弊,如果在 XML 中有配置,則 dubbo.properties 中的相應(yīng)配置項(xiàng)無(wú)效擒悬。
· Properties最后模她,相當(dāng)于缺省值,只有 XML 沒(méi)有配置時(shí)懂牧,dubbo.properties 的相應(yīng)配置項(xiàng)才會(huì)生效侈净,通常用于共享公共配置,比如應(yīng)用名僧凤。
4.2 重試次數(shù)
????失敗自動(dòng)切換畜侦,當(dāng)出現(xiàn)失敗,重試其它服務(wù)器躯保,但重試會(huì)帶來(lái)更長(zhǎng)延遲旋膳。可通過(guò)retries="2"來(lái)設(shè)置重試次數(shù)(不含第一次)途事。
· 重試次數(shù)配置如下:
<dubbo:service retries="2" />
或
<dubbo:reference retries="2" />
或
<dubbo:reference>
????<dubbo:method name="findFoo" retries="2" />
</dubbo:reference>
4.3 超時(shí)時(shí)間
????由于網(wǎng)絡(luò)或服務(wù)端不可靠验懊,會(huì)導(dǎo)致調(diào)用出現(xiàn)一種不確定的中間狀態(tài)(超時(shí))。為了避免超時(shí)導(dǎo)致客戶(hù)端資源(線(xiàn)程)掛起耗盡尸变,必須設(shè)置超時(shí)時(shí)間义图。
(1)Dubbo消費(fèi)端
全局超時(shí)配置
<dubbo:consumer timeout="5000" />
指定接口以及特定方法超時(shí)配置
<dubbo:reference interface="com.foo.BarService" timeout="2000">
????<dubbo:method name="sayHello" timeout="3000" />
</dubbo:reference>
(2)Dubbo服務(wù)端
全局超時(shí)配置
<dubbo:provider timeout="5000" />
指定接口以及特定方法超時(shí)配置
<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í)時(shí)間,合理的重試次數(shù),等等
2怕篷、在Provider配置后历筝,Consumer不配置則會(huì)使用Provider的配置值,即Provider配置可以作為Consumer的缺省值廊谓。否則漫谷,Consumer會(huì)使用Consumer端的全局設(shè)置,這對(duì)于Provider不可控的蹂析,并且往往是不合理的
配置的覆蓋規(guī)則:
? ? · 方法級(jí)配置別優(yōu)于接口級(jí)別,即小Scope優(yōu)先
? ? · Consumer端配置 優(yōu)于 Provider配置 優(yōu)于 全局配置碟婆,
? ? · 最后是Dubbo Hard Code的配置值(見(jiàn)配置文檔)
4.4 版本號(hào)
(1)當(dāng)一個(gè)接口實(shí)現(xiàn)电抚,出現(xiàn)不兼容升級(jí)時(shí),可以用版本號(hào)過(guò)渡竖共,版本號(hào)不同的服務(wù)相互間不引用蝙叛。
(2)可以按照以下的步驟進(jìn)行版本遷移:
? ? · 在低壓力時(shí)間段,先升級(jí)一半提供者為新版本
? ? · 再將所有消費(fèi)者升級(jí)為新版本
? ? · 然后將剩下的一半提供者升級(jí)為新版本
·老版本服務(wù)提供者配置:
????<dubbo:service interface="com.foo.BarService" version="1.0.0" />
·新版本服務(wù)提供者配置:
????<dubbo:service interface="com.foo.BarService" version="2.0.0" />
·老版本服務(wù)消費(fèi)者配置:
????<dubbo:reference id="barService" interface="com.foo.BarService" version="1.0.0" />
·新版本服務(wù)消費(fèi)者配置:
????<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é)議采用單一長(zhǎng)連接和NIO異步通訊借帘。
????適合于小數(shù)據(jù)量大并發(fā)的服務(wù)調(diào)用,以及服務(wù)消費(fèi)者機(jī)器數(shù)遠(yuǎn)大于服務(wù)提供者機(jī)器數(shù)的情況淌铐。Dubbo缺省協(xié)議不適合傳送大數(shù)據(jù)量的服務(wù)肺然,比如傳文件,傳視頻等腿准,除非請(qǐng)求量很低际起。
5.2 Hessian協(xié)議
(1)Hessian協(xié)議用于集成Hessian的服務(wù),Hessian底層采用Http通訊吐葱,采用Servlet暴露服務(wù)街望,Dubbo缺省內(nèi)嵌Jetty作為服務(wù)器實(shí)現(xiàn)。Hessian是Caucho開(kāi)源的一個(gè)RPC框架:
(2)基于Hessian的遠(yuǎn)程調(diào)用協(xié)議特點(diǎn)
? ? ? ? · 連接個(gè)數(shù):多連接
? ? ? ? · 連接方式:短連接
? ? ? ? · 傳輸協(xié)議:HTTP
? ? ? ? · 傳輸方式:同步傳輸
? ? ? ? · 序列化:Hessian二進(jìn)制序列化
? ? ? ? · 適用范圍:傳入傳出參數(shù)數(shù)據(jù)包較大弟跑,提供者比消費(fèi)者個(gè)數(shù)多灾前,提供者壓力較大,可傳文件孟辑。
? ? ? ? · 適用場(chǎng)景:頁(yè)面?zhèn)鬏敯ゼ祝募鬏敚蚺c原生hessian服務(wù)互操作
5.3 HTTP協(xié)議
(1)此協(xié)議采用spring的HttpInvoker的功能實(shí)現(xiàn)
(2)特點(diǎn)
? ? ? ? · 連接個(gè)數(shù):多個(gè)
? ? ? ? · 連接方式:長(zhǎng)連接
? ? ? ? · 連接協(xié)議:http
? ? ? ? · 傳輸方式:同步傳輸
? ? ? ? · 序列化:表單序列化
? ? ? ? · 適用范圍:傳入傳出參數(shù)數(shù)據(jù)包大小混合扑浸,提供者比消費(fèi)者個(gè)數(shù)多烧给,可用瀏覽器查看,可用表單或URL傳入?yún)?shù)喝噪,暫不支持傳文件础嫡。
? ? ? ? · 適用場(chǎng)景:需同時(shí)給應(yīng)用程序和瀏覽器JS使用的服務(wù)。
5.4 RMI協(xié)議
(1)采用JDK標(biāo)準(zhǔn)的java.rmi.*實(shí)現(xiàn),采用阻塞式短連接和JDK標(biāo)準(zhǔn)序列化方式
(2)Java標(biāo)準(zhǔn)的遠(yuǎn)程調(diào)用協(xié)議:
? ? ? ? · 連接個(gè)數(shù):多連接
? ? ? ? · 連接方式:短連接
? ? ? ? · 傳輸協(xié)議:TCP
? ? ? ? · 傳輸方式:同步傳輸
? ? ? ? · 序列化:Java標(biāo)準(zhǔn)二進(jìn)制序列化
? ? ? ? · 適用范圍:傳入傳出參數(shù)數(shù)據(jù)包大小混合榴鼎,消費(fèi)者與提供者個(gè)數(shù)差不多伯诬,可傳文件。
? ? ? ? · 適用場(chǎng)景:常規(guī)遠(yuǎn)程服務(wù)方法調(diào)用巫财,與原生RMI服務(wù)互操作
5.5?webservice
????基于WebService的遠(yuǎn)程調(diào)用協(xié)議盗似,集成CXF實(shí)現(xiàn),提供和原生WebService的互操作平项。多個(gè)短連接赫舒,基于HTTP傳輸,同步傳輸闽瓢,適用系統(tǒng)集成和跨語(yǔ)言調(diào)用接癌;
6 高可用
6.1 ZooKeeper宕機(jī)與Dubbo直連
(1)現(xiàn)象:zookeeper注冊(cè)中心宕機(jī),還可以消費(fèi)dubbo暴露的服務(wù)扣讼。
(2)原因:健壯性
6.2 集群下dubbo負(fù)載均衡配置
(1)在集群負(fù)載均衡時(shí)缺猛,Dubbo提供了多種均衡策略,缺省為 random 隨機(jī)調(diào)用
(2)負(fù)載均衡策略
? ? ·?Random LoadBalance??隨機(jī)椭符,按權(quán)重設(shè)置隨機(jī)概率荔燎。
????????在一個(gè)截面上碰撞的概率高,但調(diào)用量越大分布越均勻销钝,而且按概率使用權(quán)重后也比較均勻有咨,有利于動(dòng)態(tài)調(diào)整提供者權(quán)重。
? ? ·?RoundRobin LoadBalance?輪循曙搬,按公約后的權(quán)重設(shè)置輪循比率摔吏。
????????存在慢的提供者累積請(qǐng)求的問(wèn)題,比如:第二臺(tái)機(jī)器很慢纵装,但沒(méi)掛征讲,當(dāng)請(qǐng)求調(diào)到第二臺(tái)時(shí)就卡在那,久而久之橡娄,所有請(qǐng)求都卡在調(diào)到第二臺(tái)上诗箍。
? ? ·?LeastActive LoadBalance?最少活躍調(diào)用數(shù),相同活躍數(shù)的隨機(jī)
????????活躍數(shù)指調(diào)用前后計(jì)數(shù)差挽唉。
????????使慢的提供者收到更少請(qǐng)求滤祖,因?yàn)樵铰奶峁┱叩恼{(diào)用前后計(jì)數(shù)差會(huì)越大。
? ? ·?ConsistentHash LoadBalance?一致性Hash瓶籽,相同參數(shù)的請(qǐng)求總是發(fā)到同一提供者
????????當(dāng)某一臺(tái)提供者掛時(shí)匠童,原本發(fā)往該提供者的請(qǐng)求,基于虛擬節(jié)點(diǎn)塑顺,平攤到其它提供者汤求,不會(huì)引起劇烈變動(dòng)俏险。
缺省只對(duì)第一個(gè)參數(shù)Hash,如果要修改扬绪,請(qǐng)配置
????<dubbo:parameter key="hash.arguments" value="0,1" />缺省用160 份虛擬節(jié)點(diǎn)竖独,如果要修改,請(qǐng)配置
????<dubbo:parameter key="hash.nodes" value="320" />
6.3 整合Hystrix
6.3.1 服務(wù)降級(jí)
(1)概念
? ??當(dāng)服務(wù)器壓力劇增的情況下挤牛,根據(jù)實(shí)際業(yè)務(wù)情況及流量莹痢,對(duì)一些服務(wù)和頁(yè)面有策略的不處理或換種簡(jiǎn)單的方式處理,從而釋放服務(wù)器資源以保證核心交易正常運(yùn)作或高效運(yùn)作墓赴。
????可以通過(guò)服務(wù)降級(jí)功能臨時(shí)屏蔽某個(gè)出錯(cuò)的非關(guān)鍵服務(wù)竞膳,并定義降級(jí)后的返回策略。
(2)向注冊(cè)中心寫(xiě)入動(dòng)態(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表示消費(fèi)方對(duì)該服務(wù)的方法調(diào)用都直接返回 null 值诫硕,不發(fā)起遠(yuǎn)程調(diào)用顶猜。用來(lái)屏蔽不重要服務(wù)不可用時(shí)對(duì)調(diào)用方的影響。
? ? · 還可以改為mock=fail:return+null表示消費(fèi)方對(duì)該服務(wù)的方法調(diào)用在失敗后痘括,再返回 null 值,不拋異常滔吠。用來(lái)容忍不重要服務(wù)不穩(wěn)定時(shí)對(duì)調(diào)用方的影響纲菌。
6.3.2 集群容錯(cuò)
????在集群調(diào)用失敗時(shí),Dubbo提供了多種容錯(cuò)方案疮绷,缺省為 failover 重試翰舌。
· 集群容錯(cuò)模式
(1)Failover Cluster
????????失敗自動(dòng)切換,當(dāng)出現(xiàn)失敗冬骚,重試其它服務(wù)器椅贱。通常用于讀操作,但重試會(huì)帶來(lái)更長(zhǎng)延遲只冻”勇螅可通過(guò)retries="2" 來(lái)設(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)用山橄,失敗立即報(bào)錯(cuò)。通常用于非冪等性的寫(xiě)操作舍悯,比如新增記錄航棱。
(3)Failsafe Cluster
????失敗安全,出現(xiàn)異常時(shí)萌衬,直接忽略饮醇。通常用于寫(xiě)入審計(jì)日志等操作。
(4)Failback Cluster
????失敗自動(dòng)恢復(fù)秕豫,后臺(tái)記錄失敗請(qǐng)求朴艰,定時(shí)重發(fā)。通常用于消息通知操作。
(5)Forking Cluster
????并行調(diào)用多個(gè)服務(wù)器呵晚,只要一個(gè)成功即返回蜘腌。通常用于實(shí)時(shí)性要求較高的讀操作,但需要浪費(fèi)更多服務(wù)資源饵隙〈橹椋可通過(guò)forks="2" 來(lái)設(shè)置最大并行數(shù)。
(6)Broadcast Cluster
????廣播調(diào)用所有提供者金矛,逐個(gè)調(diào)用芯急,任意一臺(tái)報(bào)錯(cuò)則報(bào)錯(cuò)[2]。通常用于通知所有提供者更新緩存或日志等本地資源信息驶俊。
· 集群模式配置
按照以下示例在服務(wù)提供方和消費(fèi)方配置集群模式
<dubbo:service cluster="failsafe" />
或
<dubbo:reference cluster="failsafe" />
6.3.3?整合hystrix
????Hystrix旨在通過(guò)控制那些訪問(wèn)遠(yuǎn)程系統(tǒng)娶耍、服務(wù)和第三方庫(kù)的節(jié)點(diǎn),從而對(duì)延遲和故障提供更強(qiáng)大的容錯(cuò)能力饼酿。Hystrix具備擁有回退機(jī)制和斷路器功能的線(xiàn)程和信號(hào)隔離榕酒,請(qǐng)求緩存和請(qǐng)求打包,以及監(jiān)控和配置等功能
(1)配置spring-cloud-starter-netflix-hystrix
????spring boot官方提供了對(duì)hystrix的集成故俐,直接在pom.xml里加入依賴(lài):
? ? ? ? <dependency>
? ? ? ? ? ? <groupId>org.springframework.cloud</groupId>
? ? ? ? ? ? <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
? ? ? ? ? ? <version>1.4.4.RELEASE</version>
? ? ? ? </dependency>
????然后在Application類(lèi)上增加@EnableHystrix來(lái)啟用hystrix starter:
@SpringBootApplication
@EnableHystrix
public class ProviderApplication {
(2)配置Provider端
????在Dubbo的Provider上增加@HystrixCommand配置想鹰,這樣子調(diào)用就會(huì)經(jīng)過(guò)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端
????對(duì)于Consumer端药版,則可以增加一層method調(diào)用辑舷,并在method上配置@HystrixCommand。當(dāng)調(diào)用出錯(cuò)時(shí)槽片,會(huì)走到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 是一個(gè)RPC遠(yuǎn)程調(diào)用框架
????具體可見(jiàn)第2小節(jié)
7.2 Netty
? ? Dubbo底層采用Netty作為網(wǎng)絡(luò)通信。
7.2.1 概念
????Netty是一個(gè)異步事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框架还栓,用于快速開(kāi)發(fā)可維護(hù)的高性能協(xié)議服務(wù)器和客戶(hù)端碌廓。它極大地簡(jiǎn)化并簡(jiǎn)化了TCP和UDP套接字服務(wù)器等網(wǎng)絡(luò)編程。
? ? 同時(shí)是一個(gè)基于JAVA NIO 類(lèi)庫(kù)的異步通信框架剩盒,它的架構(gòu)特點(diǎn)是:異步非阻塞氓皱、基于事件驅(qū)動(dòng)、高性能勃刨、高可靠性和高可定制性波材。
7.2.2 應(yīng)用場(chǎng)景
(1)分布式開(kāi)源框架中dubbo、Zookeeper身隐,RocketMQ底層rpc通訊使用就是netty廷区。
(2)游戲開(kāi)發(fā)中,底層使用netty通訊贾铝。
7.2.3 TCP粘包和拆包
(1)概念
????????一個(gè)完整的業(yè)務(wù)可能會(huì)被TCP拆分成多個(gè)包進(jìn)行發(fā)送隙轻,也有可能把多個(gè)小的包封裝成一個(gè)大的數(shù)據(jù)包發(fā)送埠帕,這個(gè)就是TCP的拆包和封包問(wèn)題。
(2)由于網(wǎng)絡(luò)的復(fù)雜性玖绿,可能數(shù)據(jù)會(huì)被分離成N多個(gè)復(fù)雜的拆包/粘包的情況敛瓷,所以在做TCP服務(wù)器的時(shí)候就需要首先解決該問(wèn)題。
? ? · 消息定長(zhǎng)斑匪,報(bào)文大小固定長(zhǎng)度呐籽,不夠空格補(bǔ)全,發(fā)送和接收方遵循相同的約定蚀瘸,這樣即使粘包了通過(guò)接收方編程實(shí)現(xiàn)獲取定長(zhǎng)報(bào)文也能區(qū)分狡蝶。
sc.pipeline().addLast(new FixedLengthFrameDecoder(10));
? ? · 包尾添加特殊分隔符,例如每條報(bào)文結(jié)束都添加回車(chē)換行符(例如FTP協(xié)議)或者指定特殊字符作為報(bào)文分隔符贮勃,接收方通過(guò)特殊分隔符切分報(bào)文區(qū)分贪惹。
ByteBuf buf?= Unpooled.copiedBuffer("_mayi".getBytes());
sc.pipeline().addLast(new?DelimiterBasedFrameDecoder(1024, buf));
? ? · 將消息分為消息頭和消息體,消息頭中包含表示信息的總長(zhǎng)度(或者消息體長(zhǎng)度)的字段
7.2.4 序列化協(xié)議
(1)一種序列化協(xié)議就是Java默認(rèn)提供的序列化機(jī)制寂嘉,需要序列化的Java對(duì)象只需要實(shí)現(xiàn) Serializable / Externalizable 接口并生成序列化ID奏瞬,這個(gè)類(lèi)就能夠通過(guò) ObjectInput 和 ObjectOutput 序列化和反序列化
(2)XML
? ? · 定義:
????????XML(Extensible Markup Language)是一種常用的序列化和反序列化協(xié)議, 它歷史悠久泉孩,從1998年的1.0版本被廣泛使用至今丝格。
? ? · 優(yōu)點(diǎn)
? ? ? ? · 人機(jī)可讀性好
? ? ? ?·? 可指定元素或特性的名稱(chēng)
? ? · 缺點(diǎn)
? ? ? ? · 序列化數(shù)據(jù)只包含數(shù)據(jù)本身以及類(lèi)的結(jié)構(gòu),不包括類(lèi)型標(biāo)識(shí)和程序集信息棵譬。
? ? ? ? · 類(lèi)必須有一個(gè)將由XmlSerializer序列化的默認(rèn)構(gòu)造函數(shù)。
? ? ? ? · 只能序列化公共屬性和字段
? ? ? ? · 不能序列化方法
? ? ? ? · 文件龐大预伺,文件格式復(fù)雜订咸,傳輸占帶寬
? ? · 使用場(chǎng)景
? ? ? ? · 當(dāng)做配置文件存儲(chǔ)數(shù)據(jù)
? ? ? ? · 實(shí)時(shí)數(shù)據(jù)轉(zhuǎn)換
(3)JSON
? ? · 定義:
????????JSON(JavaScript Object Notation, JS對(duì)象標(biāo)記) 是一種輕量級(jí)的數(shù)據(jù)交換格式。它基于 ECMAScript (w3c制定的js規(guī)范)的一個(gè)子集酬诀, JSON采用與編程語(yǔ)言無(wú)關(guān)的文本格式脏嚷,但是也使用了類(lèi)C語(yǔ)言(包括C, C++瞒御, C#父叙, Java, JavaScript肴裙, Perl趾唱, Python等)的習(xí)慣,簡(jiǎn)潔和清晰的層次結(jié)構(gòu)使得 JSON 成為理想的數(shù)據(jù)交換語(yǔ)言蜻懦。
? ? · 優(yōu)點(diǎn)
? ? ? ? · 前后兼容性高
? ? ? ? · 數(shù)據(jù)格式比較簡(jiǎn)單甜癞,易于讀寫(xiě)
? ? ? ? · 序列化后數(shù)據(jù)較小,可擴(kuò)展性好宛乃,兼容性好
? ? ? ? · 與XML相比悠咱,其協(xié)議比較簡(jiǎn)單蒸辆,解析速度比較快
? ? · 缺點(diǎn)
? ? ? ? · 數(shù)據(jù)的描述性比XML差
? ? ? ? · 不適合性能要求為ms級(jí)別的情況
? ? ? ? · 額外空間開(kāi)銷(xiāo)比較大
? ? · 適用場(chǎng)景(可替代XML)
? ? ? ? · 跨防火墻訪問(wèn)
? ? ? ? · 可調(diào)式性要求高的情況
? ? ? ? · 基于Web browser的Ajax請(qǐng)求
? ? ? ? · 傳輸數(shù)據(jù)量相對(duì)小,實(shí)時(shí)性要求相對(duì)低(例如秒級(jí)別)的服務(wù)
(4)Fastjson
? ? · 定義
????????Fastjson是一個(gè)Java語(yǔ)言編寫(xiě)的高性能功能完善的JSON庫(kù)析既。它采用一種“假定有序快速匹配”的算法躬贡,把JSON Parse的性能提升到極致。
? ? · 優(yōu)點(diǎn)
? ? ? ? · 接口簡(jiǎn)單易用
? ? ? ? · 目前java語(yǔ)言中最快的json庫(kù)
? ? · 缺點(diǎn)
? ? ? ? · 過(guò)于注重快眼坏,而偏離了“標(biāo)準(zhǔn)”及功能性
? ? ? ? · 代碼質(zhì)量不高拂玻,文檔不全
? ? · 適用場(chǎng)景
? ? ? ? · 協(xié)議交互
? ? ? ? · Web輸出
? ? ? ? · Android客戶(hù)端
(5)Thrift
? ? · 定義:
????????Thrift并不僅僅是序列化協(xié)議,而是一個(gè)RPC框架空骚。它可以讓你選擇客戶(hù)端與服務(wù)端之間傳輸通信協(xié)議的類(lèi)別纺讲,即文本(text)和二進(jìn)制(binary)傳輸協(xié)議, 為節(jié)約帶寬,提供傳輸效率囤屹,一般情況下使用二進(jìn)制類(lèi)型的傳輸協(xié)議熬甚。
? ? · 優(yōu)點(diǎn)
? ? ? ? · 序列化后的體積小,速度快
? ? ? ? · 支持多種語(yǔ)言和豐富的數(shù)據(jù)類(lèi)型
? ? ? ? · 對(duì)于數(shù)據(jù)字段的增刪具有較強(qiáng)的兼容性
? ? ? ? · 支持二進(jìn)制壓縮編碼
? ? · 缺點(diǎn)
? ? ? ? · 使用者較少
? ? ? ? · 跨防火墻訪問(wèn)時(shí),不安全
? ? ? ? · 不具有可讀性肋坚,調(diào)試代碼時(shí)相對(duì)困難
? ? ? ? · 不能與其他傳輸層協(xié)議共同使用(例如HTTP)
? ? ? ? · 無(wú)法支持向持久層直接讀寫(xiě)數(shù)據(jù)乡括,即不適合做數(shù)據(jù)持久化序列化協(xié)議
? ? · 適用場(chǎng)景
? ? ? ? · 分布式系統(tǒng)的RPC解決方案
(6)Avro
? ? · 定義:
????????Avro屬于Apache Hadoop的一個(gè)子項(xiàng)目。 Avro提供兩種序列化格式:JSON格式或者Binary格式智厌。Binary格式在空間開(kāi)銷(xiāo)和解析性能方面可以和Protobuf媲美诲泌,Avro的產(chǎn)生解決了JSON的冗長(zhǎng)和沒(méi)有IDL的問(wèn)題
? ? · 優(yōu)點(diǎn)
? ? ? ? · 支持豐富的數(shù)據(jù)類(lèi)型
? ? ? ? · 簡(jiǎn)單的動(dòng)態(tài)語(yǔ)言結(jié)合功能
? ? ? ? · 具有自我描述屬性
? ? ? ? · 提高了數(shù)據(jù)解析速度
? ? ? ? · 快速可壓縮的二進(jìn)制數(shù)據(jù)形式
? ? ? ? · 可以實(shí)現(xiàn)遠(yuǎn)程過(guò)程調(diào)用RPC
? ? ? ? · 支持跨編程語(yǔ)言實(shí)現(xiàn)
? ? · 缺點(diǎn)
? ? ? ? · 對(duì)于習(xí)慣于靜態(tài)類(lèi)型語(yǔ)言的用戶(hù)不直觀
? ? · 適用場(chǎng)景
????????在Hadoop中做Hive、Pig和MapReduce的持久化數(shù)據(jù)格式
(7)Protobuf
? ? · 定義
????????protocol buffers由谷歌開(kāi)源而來(lái)铣鹏,在谷歌內(nèi)部久經(jīng)考驗(yàn)敷扫。它將數(shù)據(jù)結(jié)構(gòu)以.proto文件進(jìn)行描述,通過(guò)代碼生成工具可以生成對(duì)應(yīng)數(shù)據(jù)結(jié)構(gòu)的POJO對(duì)象和Protobuf相關(guān)的方法和屬性诚卸。
? ? · 優(yōu)點(diǎn)
? ? ? ? · 序列化后碼流小葵第,性能高
? ? ? ? · 結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)格式(XML JSON等)
? ? ? ? · 通過(guò)標(biāo)識(shí)字段的順序,可以實(shí)現(xiàn)協(xié)議的前向兼容
? ? ? ? · 結(jié)構(gòu)化的文檔更容易管理和維護(hù)
? ? · 缺點(diǎn)
? ? ? ? · 需要依賴(lài)于工具生成代碼
? ? ? ? · 支持的語(yǔ)言相對(duì)較少合溺,官方只支持Java卒密、C++ 、Python
? ? · 適用場(chǎng)景
? ? ? ? · 對(duì)性能要求高的RPC調(diào)用
? ? ? ? · 具有良好的跨防火墻的訪問(wèn)屬性
? ? ? ? · 適合應(yīng)用層對(duì)象的持久化
(8)其它
? ? · protostuff基于protobuf協(xié)議棠赛,但不需要配置proto文件哮奇,直接導(dǎo)包即
? ? · Jboss marshaling可以直接序列化java類(lèi), 無(wú)須實(shí)java.io.Serializable接口
? ? · Message pack一個(gè)高效的二進(jìn)制序列化格式
? ? · Hessian采用二進(jìn)制協(xié)議的輕量級(jí)remoting onhttp工具
? ? · kryo基于protobuf協(xié)議睛约,只支持java語(yǔ)言,需要注冊(cè)(Registration)鼎俘,然后序列化(Output),反序列化(Input)
7.2.5 Netty代碼
7.2.5.1 3.3.0版本
· 依賴(lài)
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId>
<version>3.3.0.Final</version>
</dependency>
· 服務(wù)端
class ServerHandler extends SimpleChannelHandler {
/**
* 通道關(guān)閉的時(shí)候觸發(fā)
*/
@Override
????public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
????System.out.println("channelClosed");
????}
/**
* 必須是連接已經(jīng)建立,關(guān)閉通道的時(shí)候才會(huì)觸發(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ù)器端收到客戶(hù)端消息:"+e.getMessage());
????//回復(fù)內(nèi)容
????ctx.getChannel().write("好的");
????}
}
// netty 服務(wù)器端
public class NettyServer {
????public static void main(String[] args) {
????????// 創(chuàng)建服務(wù)類(lèi)對(duì)象
????????ServerBootstrap serverBootstrap = new ServerBootstrap();
????????// 創(chuàng)建兩個(gè)線(xiàn)程池 分別為監(jiān)聽(tīng)監(jiān)聽(tīng)端口 辩涝,nio監(jiān)聽(tīng)
????????ExecutorService boos = Executors.newCachedThreadPool();
????????ExecutorService worker = Executors.newCachedThreadPool();
????????// 設(shè)置工程 并把兩個(gè)線(xiàn)程池加入中
????????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類(lèi)型.
????????????pipeline.addLast("decoder", new StringDecoder());
????????????pipeline.addLast("encoder", new StringEncoder());
????????????pipeline.addLast("serverHandler", new ServerHandler());
????????????return pipeline;
????????}
????});
????// 綁定端口號(hào)
????serverBootstrap.bind(new InetSocketAddress(9090));
????System.out.println("netty server啟動(dòng)....");
????}
}
· 客戶(hù)端
class ClientHandler extends SimpleChannelHandler {
/**
* 通道關(guān)閉的時(shí)候觸發(fā)
*/
????@Override
????public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
????????System.out.println("channelClosed");
????}
/**
* 必須是連接已經(jīng)建立,關(guān)閉通道的時(shí)候才會(huì)觸發(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ù)器端向客戶(hù)端回復(fù)內(nèi)容:"+e.getMessage());
????//回復(fù)內(nèi)容
????// ctx.getChannel().write("好的");
????}
}
// Netty客戶(hù)端
public class NettyClient {
????public static void main(String[] args) {
????????System.out.println("netty client啟動(dòng)...");
????????// 創(chuàng)建客戶(hù)端類(lèi)
????????ClientBootstrap clientBootstrap = new ClientBootstrap();
????????// 線(xiàn)程池
????????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類(lèi)型.
????????????????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("請(qǐng)輸輸入內(nèi)容...");
????????????channel.write(scanner.next());
????????}
????}
}
7.2.5.2 5.0版本
· 依賴(lài)
<!-- 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 {
/**
* 當(dāng)通道被調(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ù)給客戶(hù)端 “您好!”
????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)啟動(dòng)....");
????????// 1.創(chuàng)建2個(gè)線(xiàn)程,一個(gè)負(fù)責(zé)接收客戶(hù)端連接而芥, 一個(gè)負(fù)責(zé)進(jìn)行 傳輸數(shù)據(jù)
????????NioEventLoopGroup pGroup = new NioEventLoopGroup();
????????NioEventLoopGroup cGroup = new NioEventLoopGroup();
????????// 2. 創(chuàng)建服務(wù)器輔助類(lèi)
????????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();
????}
}
· 客戶(hù)端
class ClientHandler extends ChannelHandlerAdapter {
/**
* 當(dāng)通道被調(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("客戶(hù)端已經(jīng)啟動(dòng)....");
????????// 創(chuàng)建負(fù)責(zé)接收客戶(hù)端連接
????????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()));
????// 等待客戶(hù)端端口號(hào)關(guān)閉
????cf.channel().closeFuture().sync();
????pGroup.shutdownGracefully();
????}
}
7.3 SPI
7.3.1 Java SPI
7.3.1.1 概述
(1)概念
????SPI全稱(chēng)(service provider interface),是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機(jī)制膀值。目前市面上有很多框架都是用它來(lái)做服務(wù)的擴(kuò)展發(fā)現(xiàn)棍丐,大家耳熟能詳?shù)娜鏙DBC误辑、日志框架都有用到;
? ??簡(jiǎn)單來(lái)說(shuō)歌逢,它是一種動(dòng)態(tài)替換發(fā)現(xiàn)的機(jī)制巾钉。
(2)舉個(gè)簡(jiǎn)單的例子,
? ? · 如果我們定義了一個(gè)規(guī)范秘案,需要第三方廠商去實(shí)現(xiàn)砰苍,
? ? · 那么對(duì)于我們應(yīng)用方來(lái)說(shuō),只需要集成對(duì)應(yīng)廠商的插件阱高,既可以完成對(duì)應(yīng)規(guī)范的實(shí)現(xiàn)機(jī)制赚导。
? ? · 形成一種插拔式的擴(kuò)展手段。
7.3.1.2 SPI規(guī)范
(1)需要在classpath下創(chuàng)建一個(gè)目錄赤惊,該目錄命名必須是:META-INF/services
(2)在該目錄下創(chuàng)建一個(gè)properties文件吼旧,該文件需要滿(mǎn)足以下幾個(gè)條件
? ? ? ? · 文件名必須是擴(kuò)展的接口的全路徑名稱(chēng)
? ? ? ? · 文件內(nèi)部描述的是該擴(kuò)展接口的所有實(shí)現(xiàn)類(lèi)
? ? ? ? · 文件的編碼格式是UTF-8
(3)通過(guò)java.util.ServiceLoader的加載機(jī)制來(lái)發(fā)現(xiàn)
7.3.1.3 SPI實(shí)例
????JDK官方提供了java.sql.Driver這個(gè)驅(qū)動(dòng)擴(kuò)展點(diǎn),但是你們并沒(méi)有看到JDK中有對(duì)應(yīng)的Driver實(shí)現(xiàn)未舟。
????以連接Mysql為例圈暗,我們需要添加mysql-connector-java依賴(lài)。你們可以在這個(gè)jar包中找到SPI的配置信息裕膀。所以java.sql.Driver由各個(gè)數(shù)據(jù)庫(kù)廠商自行實(shí)現(xiàn)员串。
7.3.1.4 SPI的缺點(diǎn)
(1)JDK標(biāo)準(zhǔn)的SPI會(huì)一次性加載實(shí)例化擴(kuò)展點(diǎn)的所有實(shí)現(xiàn)
????????就是如果你在META-INF/service下的文件里面加了N個(gè)實(shí)現(xiàn)類(lèi),那么JDK啟動(dòng)的時(shí)候都會(huì)一次性全部加載昼扛。
????????那么如果有的擴(kuò)展點(diǎn)實(shí)現(xiàn)初始化很耗時(shí)或者如果有些實(shí)現(xiàn)類(lèi)并沒(méi)有用到寸齐,那么會(huì)很浪費(fèi)資源
(2)如果擴(kuò)展點(diǎn)加載失敗,會(huì)導(dǎo)致調(diào)用方報(bào)錯(cuò),而且這個(gè)錯(cuò)誤很難定位到是這個(gè)原因
7.3.2 Dubbo SPI
(1)Dubbo擴(kuò)展的新特性
? ? · 內(nèi)嵌在dubbo中
? ? · 支持通過(guò)SPI文件聲明擴(kuò)展實(shí)現(xiàn)(interfce必須有@SPI注解),
格式為extensionName=extensionClassName,extensionName類(lèi)似于spring的beanName
? ? · 支持通過(guò)配置指定extensionName來(lái)從SPI文件中選出對(duì)應(yīng)實(shí)現(xiàn)
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("defineProtocol")
? ? 通過(guò)配置文件中的權(quán)限定名,加載實(shí)現(xiàn)類(lèi)吞鸭。在運(yùn)行時(shí),可以動(dòng)態(tài)為接口替換實(shí)現(xiàn)類(lèi)逗载。
(2)源碼分析
????ServiceConfig類(lèi)中的一行代碼:
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class). getAdaptiveExtension();
· Protocol
????一個(gè)是在類(lèi)級(jí)別上的@SPI(“dubbo”)塘安,@SPI?表示當(dāng)前這個(gè)接口是一個(gè)擴(kuò)展點(diǎn),可以實(shí)現(xiàn)自己的擴(kuò)展實(shí)現(xiàn)殴边,默認(rèn)的擴(kuò)展點(diǎn)是DubboProtocol憎茂。
????另一個(gè)是@Adaptive,表示一個(gè)自適應(yīng)擴(kuò)展點(diǎn)锤岸,在方法級(jí)別上竖幔,會(huì)動(dòng)態(tài)生成一個(gè)適配器類(lèi)。
@SPI("dubbo")
publicinterfaceProtocol{
/**
? ? * 獲取缺省端口是偷,當(dāng)用戶(hù)沒(méi)有配置端口時(shí)使用拳氢。
? ? *
*@return缺省端口
? ? */
intgetDefaultPort();
/**
? ? * 暴露遠(yuǎn)程服務(wù):<br>
? ? * 1. 協(xié)議在接收請(qǐng)求時(shí)募逞,應(yīng)記錄請(qǐng)求來(lái)源方地址信息:RpcContext.getContext().setRemoteAddress();<br>
? ? * 2. export()必須是冪等的,也就是暴露同一個(gè)URL的Invoker兩次馋评,和暴露一次沒(méi)有區(qū)別放接。<br>
? ? * 3. export()傳入的Invoker由框架實(shí)現(xiàn)并傳入,協(xié)議不需要關(guān)心留特。<br>
? ? *
*@param? ? 服務(wù)的類(lèi)型
*@paraminvoker 服務(wù)的執(zhí)行體
*@returnexporter 暴露服務(wù)的引用纠脾,用于取消暴露
*@throwsRpcException 當(dāng)暴露服務(wù)出錯(cuò)時(shí)拋出,比如端口已占用
? ? */
@Adaptive
Exporterexport(Invoker<T> invoker)throwsRpcException;
/**
? ? * 引用遠(yuǎn)程服務(wù):<br>
? ? * 1. 當(dāng)用戶(hù)調(diào)用refer()所返回的Invoker對(duì)象的invoke()方法時(shí)蜕青,協(xié)議需相應(yīng)執(zhí)行同URL遠(yuǎn)端export()傳入的Invoker對(duì)象的invoke()方法苟蹈。<br>
? ? * 2. refer()返回的Invoker由協(xié)議實(shí)現(xiàn),協(xié)議通常需要在此Invoker中發(fā)送遠(yuǎn)程請(qǐng)求右核。<br>
? ? * 3. 當(dāng)url中有設(shè)置check=false時(shí)慧脱,連接失敗不能拋出異常,并內(nèi)部自動(dòng)恢復(fù)蒙兰。<br>
? ? *
*@param? 服務(wù)的類(lèi)型
*@paramtype 服務(wù)的類(lèi)型
*@paramurl? 遠(yuǎn)程服務(wù)的URL地址
*@returninvoker 服務(wù)的本地代理
*@throwsRpcException 當(dāng)連接服務(wù)提供方失敗時(shí)拋出
? ? */
@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è)計(jì)?
? ? 總體分為Business挠他、RPC和Remoting三層設(shè)計(jì)
· Service服務(wù)接口層:該層是與實(shí)際業(yè)務(wù)邏輯相關(guān)的扳抽,根據(jù)服務(wù)提供方和服務(wù)消費(fèi)方的業(yè)務(wù)設(shè)計(jì)對(duì)應(yīng)的接口和實(shí)現(xiàn)。
· config配置層:對(duì)外配置接口殖侵,以 ServiceConfig, ReferenceConfig 為中心贸呢,可以直接初始化配置類(lèi),也可以通過(guò) spring 解析配置生成配置類(lèi)
· proxy服務(wù)代理層:服務(wù)接口透明代理拢军,生成服務(wù)的客戶(hù)端 Stub 和服務(wù)器端 Skeleton, 以 ServiceProxy 為中心楞陷,擴(kuò)展接口為 ProxyFactory
· registry注冊(cè)中心層:封裝服務(wù)地址的注冊(cè)與發(fā)現(xiàn),以服務(wù) URL 為中心茉唉,擴(kuò)展接口為 RegistryFactory, Registry, RegistryService
· cluster路由層:封裝多個(gè)提供者的路由及負(fù)載均衡固蛾,并橋接注冊(cè)中心,以 Invoker 為中心度陆,擴(kuò)展接口為 Cluster, Directory, Router, LoadBalance
· monitor監(jiān)控層:RPC 調(diào)用次數(shù)和調(diào)用時(shí)間監(jiān)控艾凯,以 Statistics 為中心,擴(kuò)展接口為 MonitorFactory, Monitor, MonitorService
· protocol遠(yuǎn)程調(diào)用層:封裝 RPC 調(diào)用懂傀,以 Invocation, Result 為中心趾诗,擴(kuò)展接口為 Protocol, Invoker, Exporter
· 信息交換層:封裝請(qǐng)求響應(yīng)模式,同步轉(zhuǎn)異步蹬蚁,以 Request, Response 為中心恃泪,擴(kuò)展接口為 Exchanger, ExchangeChannel, ExchangeClient, ExchangeServer
· 網(wǎng)絡(luò)傳輸層:抽象 mina 和 netty 為統(tǒng)一接口郑兴,以 Message 為中心,擴(kuò)展接口為 Channel, Transporter, Client, Server, Codec
· serialize數(shù)據(jù)序列化層:可復(fù)用的一些工具悟泵,擴(kuò)展接口為 Serialization, ObjectInput, ObjectOutput, ThreadPool
8.2 dubbo原理-啟動(dòng)解析杈笔、加載配置信息
(1)解析XML
????在dubboNamespaceHandler中將<dubbo:provider>等解析為對(duì)應(yīng)的xxxConfig對(duì)象.
(2)標(biāo)簽內(nèi)容解析,封裝為beanDefinition對(duì)象
8.3 dubbo原理 -服務(wù)暴露
? ? Invoker是用戶(hù)接口的代理對(duì)象實(shí)例糕非,會(huì)經(jīng)過(guò)層層包裝蒙具。
????在使用Protocol時(shí),會(huì)調(diào)用兩個(gè)protocol朽肥。一個(gè)是DubboProtocol禁筏,其對(duì)應(yīng)的DubboExporter打開(kāi)Nttey服務(wù)器,監(jiān)聽(tīng)對(duì)應(yīng)的服務(wù)提供者端口衡招;一個(gè)是RegistryProtocol篱昔,其對(duì)應(yīng)的RegistryExporter對(duì)將提供者地址(服務(wù)器地址,如http://127.0.0.1:20880)作為key,實(shí)例化的Invoker(具體服務(wù)接口的實(shí)現(xiàn)serviceImpl)作為value添加到注冊(cè)表中始腾,并向Zookeeper注冊(cè)中心注冊(cè)服務(wù)(添加節(jié)點(diǎn)信息)州刽。
8.4 dubbo原理 -服務(wù)引用
????首先ReferenceConfig類(lèi)的init方法調(diào)用Protocol的refer方法生成Invoker實(shí)例,這是服務(wù)消費(fèi)的關(guān)鍵浪箭。接下來(lái)把Invoker轉(zhuǎn)換為客戶(hù)端需要的接口(如:HelloWorld)穗椅。
8.5 dubbo原理 -服務(wù)調(diào)用
? ? 集群容錯(cuò)模式選擇:
9 Dubbox
10 基于Spring的Dubbo整合
10.1 依賴(lài)
<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)用信息,用于計(jì)算依賴(lài)關(guān)系 -->
<dubbo:application name="provider" />
<!-- 使用zookeeper注冊(cè)中心暴露服務(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" />
<!-- 具體的實(shí)現(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("客戶(hù)端有人來(lái)消費(fèi)了....");
????????if (id==1) {
????????????return "我";
????????}
????????if (id==2) {
????????????return "扎克伯格";
????????}
????????if (id==3) {
????????????return "馬化騰";
????????}
????????return "沒(méi)有找到";
????}
}
10.2.3 啟動(dòng)
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">
<!-- 消費(fèi)方應(yīng)用名奶栖,用于計(jì)算依賴(lài)關(guān)系匹表,不是匹配條件,不要與提供方一樣 -->
<dubbo:application name="consumer" />
<!-- 使用multicast廣播注冊(cè)中心暴露發(fā)現(xiàn)服務(wù)地址 -->
<dubbo:registry protocol="zookeeper" address="zookeeper://127.0.0.1:2181" />
<!-- 生成遠(yuǎn)程服務(wù)代理宣鄙,可以和本地bean一樣使用demoService -->
<dubbo:reference id="userService" interface="com.itmayiedu.service.UserService" />
</beans>
10.3.2 啟動(dòng)
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("consumer.xml");
UserService userService = (UserService) app.getBean("userService");
String name = userService.getList(1);
System.out.println("name:" + name);