自己做產(chǎn)品品牌網(wǎng)站谷歌官網(wǎng)網(wǎng)址
04-Dubbo的通信協(xié)議
Dubbo 支持的通信協(xié)議
Dubbo 框架提供了自定義的高性能 RPC 通信協(xié)議:
-
基于 TCP 的 Dubbo2 協(xié)議
-
基于 HTTP/2 的 Triple 協(xié)議
Dubbo 框架是不和任何通信協(xié)議綁定的,對通信協(xié)議的支持非常靈活,支持任意的第三方協(xié)議,如:gRPC、Thrift、REST、JsonRPC、Hessian2
通信協(xié)議是什么?網(wǎng)絡(luò)通信框架?
通信協(xié)議
就是對通信傳輸?shù)臄?shù)據(jù)進(jìn)行定義,表示每一位的含義是什么
這里再說一下 Dubbo 中使用的網(wǎng)絡(luò)通信框架有以下三個(gè):Netty、Mina、Grizzly
HTTP/1.x 通信協(xié)議的缺點(diǎn)
我們知道,Dubbo 中的 RPC 通信是比較快的,因?yàn)?Dubbo 就是為了追求極致的 RPC 通信性能,在 Dubbo2 版本中有 Dubbo 協(xié)議,在 Dubbo3 版本推出了新的 Tripple 協(xié)議
-
為什么 Dubbo 不選擇 HTTP/1.x 協(xié)議呢?
HTTP/1.x 的優(yōu)點(diǎn)就是它的 通用性
,基本上所有的應(yīng)用都可以接收 HTTP/1.x 請求并進(jìn)行解析,但是缺點(diǎn)也很顯然 速度慢
,HTTP/1.x 協(xié)議速度慢主要是由兩個(gè)方面:
-
第一方面:傳輸?shù)臒o用數(shù)據(jù)很多,降低了數(shù)據(jù)傳輸效率
-
第二方面:Http/1.x 中,在一條 Socket 連接中,一次只能發(fā)送一個(gè) HTTP 請求,然后必須等收到了響應(yīng)之后,再發(fā)送下一個(gè) HTTP 請求
如果不太理解的話,可以想象一下我們在 Java 中通過 HttpClient 發(fā)送 HTTP 請求的過程,是不是必須要
httpclient.get('請求url')
發(fā)起請求,請求之后必須要拿到該 httpclient 的響應(yīng)進(jìn)行解析,而不可以連續(xù)發(fā)送兩個(gè)請求如下:httpclient.get('請求1');
httpclient.get('請求2');
因此呢,針對以上的缺點(diǎn),Dubbo 就推出了自己的通信協(xié)議
Dubbo 協(xié)議
Dubbo2.x 中默認(rèn)采用 Dubbo 協(xié)議,Dubbo 協(xié)議是采用 單一長連接
和 NIO 異步通訊
,因此適合 小數(shù)據(jù)量高并發(fā)
的服務(wù)調(diào)用,不適合傳送大數(shù)據(jù)量的服務(wù)(如視頻)
Dubbo 協(xié)議是 基于 TCP
傳輸層協(xié)議的 RPC 通信協(xié)議,目的就是為了簡化 RPC 調(diào)用的復(fù)雜性,提高通信效率
-
Dubo 協(xié)議針對 HTTP/1.x 協(xié)議的優(yōu)化
Dubbo 協(xié)議針對于 HTTP/1.x 協(xié)議的缺點(diǎn),做出了優(yōu)化,在 Dubbo 協(xié)議中盡量避免了傳輸無用的字節(jié),并且還可以基于一個(gè) Socket 連接同時(shí)發(fā)送多個(gè) Dubbo 請求
-
Dubbo 協(xié)議的組成如下

如上就是 Dubbo 協(xié)議的定義
-
前 16 bit 是 Magic,也就是魔數(shù),標(biāo)識(shí)這是 Dubbo2 協(xié)議,以及版本號(hào),Magic 一般都是固定的:0xdabb
接下來每個(gè) bit 或者多個(gè) bit 組合起來有不同的含義,這里就不贅述了
通信協(xié)議就是對拿到的一系列 bit 數(shù)據(jù)進(jìn)行語義解析,拿到遠(yuǎn)程服務(wù)想要傳輸過來的數(shù)據(jù),了解通信協(xié)議到底是用來做什么的就可以了!
-
為什么 Dubbo 協(xié)議采用異步單一長連接?
因?yàn)楝F(xiàn)在的服務(wù)通常情況下是 服務(wù)提供者比較少,服務(wù)消費(fèi)者比較多
通過單一長連接可以減少連接握手驗(yàn)證等操作,避免服務(wù)消費(fèi)者過多時(shí),直接將提供者給壓垮
并且使用異步 IO、復(fù)用線程池,避免 C10K 問題(C10K 問題就是如何讓服務(wù)器可以同時(shí)處理 10K 并發(fā)的 TCP 連接)
不過現(xiàn)在由于硬件水平的提升,C10K 基本上不是問題了,而逐漸演變成了 C100K 的問題
-
為什么 Dubbo 協(xié)議不建議傳大數(shù)據(jù)包?
因?yàn)?Dubbo 協(xié)議采用單一長連接,如果每次請求的數(shù)據(jù)包大小為 500KB,假設(shè)網(wǎng)絡(luò)為千兆網(wǎng)卡(1000Mb=128MB),根據(jù)測試經(jīng)驗(yàn)發(fā)現(xiàn)每條連接最多只能壓滿 7MB,因此,理論上一個(gè)服務(wù)提供者需要 20 個(gè)服務(wù)消費(fèi)者才能壓滿網(wǎng)卡
如果每次傳輸?shù)臄?shù)據(jù)包較大 ,假設(shè)為 500KB,那么單個(gè)消費(fèi)者的最大 TPS(每秒事務(wù)處理數(shù))為:128MB / 500KB = 262
單個(gè)消費(fèi)調(diào)用者處理單個(gè)提供者的最大 TPS 為 7MB / 500KB = 14,也就是說每秒只可以調(diào)用 14 次提供者,次數(shù)較少
如果可以接受的話,可以考慮使用 Dubbo 協(xié)議傳輸大的數(shù)據(jù)包,否則, 網(wǎng)絡(luò)帶寬
將成為服務(wù)調(diào)用的性能瓶頸!

-
Dubbo 協(xié)議所存在的缺點(diǎn)
Dubbo 協(xié)議目前所存在的缺點(diǎn)就是 通用性不足
,如果其他應(yīng)用想要調(diào)用 Dubbo 服務(wù),就必須按照 Dubbo 協(xié)議的格式去發(fā)送請求,因此 Dubbo 協(xié)議在通用性上不如 HTTP 協(xié)議
Tripple 協(xié)議
因此 Dubbo 框架為了提升協(xié)議的通用性,可以和 SpringCloud 以及其他語言應(yīng)用進(jìn)行通信,在 Dubbo3.x 版本推出了基于 HTTP/2 的 Triple 協(xié)議,也就是說 Tripple 協(xié)議在發(fā)送數(shù)據(jù)時(shí)會(huì)根據(jù) HTTP/2 協(xié)議的格式來發(fā)送!
HTTP/2 兼容 HTTP/1,并且性能更好,在 兼容性
和 性能
上都有所提升!
Tripple 協(xié)議是 Dubbo3 推出的主力協(xié)議,Tripple 的含義就是三,表示是第三代,Tripple 協(xié)議的特點(diǎn):
1、Tripple 協(xié)議是 Dubbo3 設(shè)計(jì)的基于 HTTP2 的 RPC 通信協(xié)議規(guī)范, 通用性
能有所提升,并且由于是基于 HTTP/2 的,因此 性能
上也要比 HTTP/1.x 要好一些
2、Tripple 協(xié)議支持 流式調(diào)用
由于 Tripple 協(xié)議是基于 HTTP/2 的,因此這里對 HTTP/2 協(xié)議再介紹一下
-
介紹一下 HTTP/2 協(xié)議
HTTP/2 協(xié)議是對 HTTP/1 協(xié)議的升級,HTTP/1 的缺點(diǎn)就是任何一個(gè)普通的 HTTP 請求,就算只發(fā)送很短的一個(gè)字符串,也要帶上一個(gè)請求頭,并且這個(gè)請求頭比較大,占用多個(gè)字節(jié),導(dǎo)致數(shù)據(jù)傳輸效率不高!
HTTP/1 協(xié)議的請求格式如下:

可以看到下邊紅色部分的實(shí)體也就是我們需要傳輸?shù)臄?shù)據(jù)
而上邊都是請求頭中的一些數(shù)據(jù),像一些空格、換行符都是沒有必要存在的字符,因此在 HTTP/2 中做了優(yōu)化
HTTP/2 中所做的優(yōu)化:
1、HTTP/2 中 將請求和響應(yīng)數(shù)據(jù)分割為更小的幀
2、并且 引入 HPACK 算法對標(biāo)頭壓縮
,減小標(biāo)頭大小
3、并且 支持 Stream
:

1、幀長度:總共 24 bit,表示的最大數(shù)字為 2^24bit = 16M,所以一個(gè)幀最大為:9B(頭部) + 16M(內(nèi)容)
2、幀類型,8bit,分為數(shù)據(jù)幀和控制幀
? 2.1、數(shù)據(jù)幀分為:HEADERS 幀和 DATA 幀,用來傳輸請求頭、請求體
? 2.2、控制幀分為:SETTINGS、PING、PRIORITY,用來進(jìn)行管理
3、標(biāo)志位,8bit,可以用來表示當(dāng)前幀是請求的最后一幀,方便服務(wù)端解析
4、流標(biāo)識(shí)符,32bit,表示 Stream ID,最高位保留不用
5、實(shí)際傳輸?shù)臄?shù)據(jù),如果幀類型是 HEADERS,則這里存儲(chǔ)的就是請求頭,如果幀類型是 DATA,則這里存儲(chǔ)的就是請求體
-
HTTP/2 中的 Stream 流
HTTP/2 除了使用 HPACK 來壓縮請求頭的大小,他還支持 Stream,通過 Stream 可以極大程度上提升 HTTP/2 的并發(fā)度
在 HTTP/1 中,在一個(gè) TCP 連接上,只能先發(fā)送一個(gè)請求,之后必須等這個(gè)請求相應(yīng)之后,才可以發(fā)送第二個(gè)請求,這樣速度太慢了
因此,在 HTTP/2 中,可以在一個(gè) TCP 連接上維護(hù)多個(gè) Stream,這樣就可以并發(fā)的給服務(wù)端發(fā)送多個(gè)幀了
比如說,客戶端要給服務(wù)端發(fā)送 3 個(gè)請求,如果只建立一個(gè) Stream,那么每次只能發(fā)送 1 個(gè)請求,之后等拿到了響應(yīng)結(jié)果之后,再發(fā)送第 2 個(gè)請求
如果建立了三個(gè) Stream,客戶端就可以使用三個(gè)線程,同時(shí)將 3 個(gè)請求通過這三個(gè) Stream 發(fā)送給服務(wù)端去
-
在 HTTP/2 中客戶端發(fā)送請求的流程為:
1、新建 TCP 連接
2、新建一個(gè) Stream:生成一個(gè)新的 StreamID,生成一個(gè)控制幀,幀里記錄了生成的 StreamID,通過 TCP 連接發(fā)送出去
3、發(fā)送請求的請求頭:生成要發(fā)送請求的 HEADERS 幀,使用 ASCII 編碼,HPACK 進(jìn)行壓縮,將壓縮后的數(shù)據(jù)放到幀的 Payload 區(qū)域,記錄 StreamID,通過 TCP 連接發(fā)送出去
4、發(fā)送請求的請求體:將要發(fā)送請求的請求體中的數(shù)據(jù)按照指定的壓縮算法(請求中指定的壓縮算法,比如 gzip)進(jìn)行壓縮,使用壓縮后的數(shù)據(jù)生成生成 DATA 幀,記錄 StreamID,通過 TCP 連接發(fā)送出去
-
在 HTTP/2 中服務(wù)端接收請求的流程為:
1、服務(wù)端從 TCP 連接中不斷接受幀
2、當(dāng)接收到控制幀,表示客戶端要和服務(wù)端建立一個(gè) Stream,服務(wù)端記錄下來 StreamID,在 Dubbo3 中會(huì)生成一個(gè) ServerStreamObserver 對象
3、當(dāng)接收到 HEADERS 幀,取出 StreamID,找到對應(yīng)的 ServerStreamObserver 對象,解壓之后得到請求頭,將請求頭信息存入該 ServerStreamObserver 對象中
4、當(dāng)接收到 DATA 幀,取出 StreamID,找到對應(yīng)的 ServerStreamObserver 對象,將請求體解壓之后,按照業(yè)務(wù)邏輯處理請求體
5、處理完之后,將結(jié)果生成 HEADERS 幀和 DATA 幀發(fā)送給客戶端
-
基于 HTTP/2 的數(shù)據(jù)幀機(jī)制,Tripple 協(xié)議支持 UNARY、SERVER_STREAM、BI_STREAM 三種模式
1、UNARY :最普通的,服務(wù)端接受完所有請求幀之后,才處理數(shù)據(jù)
2、SERVER_STREAM :服務(wù)端流式調(diào)用,服務(wù)端接收完所有請求幀之后,才處理數(shù)據(jù),但是可以多次發(fā)送響應(yīng) DATA 幀給客戶端
3、BI_STREAM :雙端流式調(diào)用,客戶端可以多次發(fā)送 DATA 幀,服務(wù)端不斷接收 DATA 幀進(jìn)行處理,并且將處理結(jié)果作為響應(yīng) DATA 幀多次發(fā)送給客戶端,客戶端收到之后,也會(huì)立即進(jìn)行處理(也就是客戶端和服務(wù)端都可以不斷接收 DATA 幀,進(jìn)行處理)
-
接下來說一下 Tripple 協(xié)議支持的流式調(diào)用(就是基于 HTTP/2 的幀實(shí)現(xiàn))
流式調(diào)用是在 Dubbo3.x 版本新增的,如果我們需要使用流式調(diào)用的話,需要自己定義對應(yīng)的方法
首先引入一下需要使用的類 StreamObserver
的依賴:
<dependency>
??<groupId>org.apache.dubbo</groupId>
????<artifactId>dubbo-common</artifactId>
????<version>3.0.7</version>
</dependency>
并且引入一下 Tripple 協(xié)議的依賴
<dependency>
??<groupId>org.apache.dubbo</groupId>
????<artifactId>dubbo-rpc-tripple</artifactId>
????<version>3.0.7</version>
</dependency>
比如說,我們在 UserService 接口中定義了流式調(diào)用:
public?interface?UserService?{
????String?hello(String?name);
????
????//?服務(wù)端流式調(diào)用
????default?void?helloServerStream(String?name,?StreamObserver<String>?response)?{}
????//?雙端流式調(diào)用
????default?StreamObserver<String>?helloStream(StreamObserver<String>?response)?{return?response;}
}
服務(wù)端流式調(diào)用 的話,返回值需要為 void
,參數(shù)中需要有 StreamObserver<String>
,
服務(wù)端對應(yīng)接口實(shí)現(xiàn)方法為:
//?UserServiceImpl?implements?UserService
@Override
public?void?sayHelloServerStream(String?name,?StreamObserver<String>?response)?{
????response.onNext(name?+?"?hello");
????response.onNext(name?+?"?world");
????response.onCompleted();
}
客戶端調(diào)用者代碼為:
userService.helloServerStream("11",?new?StreamObserver<String>(){
????@Override
????public?void?onNext(String?data)?{
????????//?服務(wù)端返回的數(shù)據(jù)
????}
????@Override
????public?void?onError(Throwable?throwable)?{}
????@Override
????public?void?onCompleted(String?data)?{
????????//?服務(wù)端執(zhí)行完畢
????}
})
雙端流式調(diào)用 的話,返回值和參數(shù)都要有 StreamObserver
服務(wù)端對應(yīng)接口實(shí)現(xiàn)方法為:
//?UserServiceImpl?implements?UserService
@Override
public?StreamObserver<String>?sayHelloStream(StreamObserver<String>?response)?{
????return?new?StreamObserver<String>()?{
????????@Override
????????public?void?onNext(String?data)?{
????????????//?接收客戶端發(fā)送的數(shù)據(jù)
????????????response.onNext("result:"?+?data);
????????}
????????@Override
????????public?void?onError(Throwable?throwable)?{}
????????@Override
????????public?void?onCompleted(String?data)?{
????????????//?服務(wù)端執(zhí)行完畢
????????}
????}
}
客戶端調(diào)用者代碼為:
StreamObserver<String>?streamObserver?=?userService.sayHelloStream(new?StreamObserver<String>()?{
????@Override
????public?void?onNext(String?data)?{
?????????System.out.println("接收到響應(yīng)數(shù)據(jù):"+?data);
????}
????@Override
????public?void?onError(Throwable?throwable)?{}
????@Override
????public?void?onCompleted(String?data)?{
????????//?接收數(shù)據(jù)完畢
????}
})
//?客戶端發(fā)送數(shù)據(jù)
streamObserver.onNext("第一次發(fā)送數(shù)據(jù)");
streamObserver.onNext("第二次發(fā)送數(shù)據(jù)");
streamObserver.onCompleted();
-
接下來總結(jié)一下 Tripple 協(xié)議中的流式調(diào)用的優(yōu)點(diǎn)以及應(yīng)用場景
首先,流式調(diào)用的優(yōu)點(diǎn)就是 客戶端可以多次向服務(wù)端發(fā)送消息,并且服務(wù)端也可以多次接收
,通過 onNext 方法多次發(fā)送,比如用戶在處理完一部分?jǐn)?shù)據(jù)之后,將這一部分?jǐn)?shù)據(jù)發(fā)送給服務(wù)端,之后再去處理下一部分?jǐn)?shù)據(jù),避免了一次發(fā)送很多數(shù)據(jù)的情況
流式調(diào)用的應(yīng)用場景為:接口需要發(fā)送大量數(shù)據(jù),這些數(shù)據(jù)通過一個(gè) RPC 請求無法發(fā)送完畢,需要分批發(fā)送,并且需要保證發(fā)送的有序性
獲取更多干貨內(nèi)容,記得關(guān)注我哦。
本文由 mdnice 多平臺(tái)發(fā)布