RPC-client異步收發(fā)核心細節(jié)

2018-09-06 17:18 更新
通過上篇文章的介紹,知道了要實施微服務(wù),首先要搞定RPC框架,RPC框架分為客戶端部分與服務(wù)端部分。
RPC框架

RPC-client的部分又分為:

(1)序列化反序列化的部分(上圖中的1、4)

(2)發(fā)送字節(jié)流與接收字節(jié)流的部分(上圖中的2、3)

前一篇文章討論了序列化與范序列化的細節(jié),這一篇文章將討論發(fā)送字節(jié)流與接收字節(jié)流的部分。


客戶端調(diào)用又分為同步調(diào)用與異步調(diào)用

同步調(diào)用的代碼片段為:

Result = Add(Obj1, Obj2);// 得到Result之前處于阻塞狀態(tài)

異步調(diào)用的代碼片段為:

Add(Obj1, Obj2, callback);// 調(diào)用后直接返回,不等結(jié)果

處理結(jié)果通過回調(diào)得到:

callback(Result){// 得到處理結(jié)果后會調(diào)用這個回調(diào)函數(shù)

         …
}
這兩個調(diào)用方式,RPC-client里,處理方式也不一樣,下文逐一敘述。 

RPC-client同步調(diào)用

RPC-client同步調(diào)用
所謂同步調(diào)用,在得到結(jié)果之前,一直處于阻塞狀態(tài),會一直占用一個工作線程,上圖簡單的說明了一下組件、交互、流程步驟。

上圖中的左邊大框,就代表了調(diào)用方的一個工作線程

左邊粉色中框,代表了RPC-client組件。

右邊橙色框,代表了RPC-server。

藍色兩個小框,代表了同步RPC-client兩個核心組件,序列化組件與連接池組件。

白色的流程小框,以及箭頭序號1-10,代表整個工作線程的串行執(zhí)行步驟

1)業(yè)務(wù)代碼發(fā)起RPC調(diào)用,Result=Add(Obj1,Obj2)

2)序列化組件,將對象調(diào)用序列化成二進制字節(jié)流,可理解為一個待發(fā)送的包packet1

3)通過連接池組件拿到一個可用的連接connection

4)通過連接connection將包packet1發(fā)送給RPC-server

5)發(fā)送包在網(wǎng)絡(luò)傳輸,發(fā)給RPC-server

6)響應(yīng)包在網(wǎng)絡(luò)傳輸,發(fā)回給RPC-client

7)通過連接connection從RPC-server收取響應(yīng)包packet2

8)通過連接池組件,將conneciont放回連接池

9)序列化組件,將packet2范序列化為Result對象返回給調(diào)用方

10)業(yè)務(wù)代碼獲取Result結(jié)果,工作線程繼續(xù)往下走


RPC框架需要支持負載均衡、故障轉(zhuǎn)移、發(fā)送超時,這些特性都是通過連接池組件去實現(xiàn)的。

連接池組件

連接池組件
典型連接池組件對外提供的接口為:
int ConnectionPool::init(…);
Connection ConnectionPool::getConnection();
intConnectionPool::putConnection(Connection t);

【INIT】

和下游RPC-server(一般是一個集群),建立N個tcp長連接,即所謂的連接“池”

【getConnection】

從連接“池”中拿一個連接,加鎖(置一個標志位),返回給調(diào)用方

【putConnection】

將一個分配出去的連接放回連接“池”中,解鎖(也是置一個標志位)


如何實現(xiàn)負載均衡?

回答:連接池中建立了與一個RPC-server集群的連接,連接池在返回連接的時候,需要具備隨機性。


如何實現(xiàn)故障轉(zhuǎn)移?

回答:連接池中建立了與一個RPC-server集群的連接,當連接池發(fā)現(xiàn)某一個機器的連接異常后,需要將這個機器的連接排除掉,返回正常的連接,在機器恢復后,再將連接加回來。


如何實現(xiàn)發(fā)送超時?

回答:因為是同步阻塞調(diào)用,拿到一個連接后,使用帶超時的send/recv即可實現(xiàn)帶超時的發(fā)送和接收。


總的來說,同步的RPC-client的實現(xiàn)是相對比較容易的,序列化組件、連接池組件配合多工作線程數(shù),就能夠?qū)崿F(xiàn)。還有一個問題,就是【“工作線程數(shù)設(shè)置多少最為合適?”】,這個問題在之前的文章中討論過,此處不再深究。

RPC-client異步回調(diào)

RPC-client異步回調(diào)
所謂異步回調(diào),在得到結(jié)果之前,不會處于阻塞狀態(tài),理論上任何時間都沒有任何線程處于阻塞狀態(tài),因此異步回調(diào)的模型,理論上只需要很少的工作線程與服務(wù)連接就能夠達到很高的吞吐量。

上圖中左邊的框框,是少量工作線程(少數(shù)幾個就行了)進行調(diào)用與回調(diào)。

中間粉色的框框,代表了RPC-client組件。

右邊橙色框,代表了RPC-server。

藍色六個小框,代表了異步RPC-client六個核心組件:上下文管理器,超時管理器,序列化組件,下游收發(fā)隊列,下游收發(fā)線程,連接池組件

白色的流程小框,以及箭頭序號1-17,代表整個工作線程的串行執(zhí)行步驟

1)業(yè)務(wù)代碼發(fā)起異步RPC調(diào)用,Add(Obj1,Obj2, callback)

2)上下文管理器,將請求,回調(diào),上下文存儲起來

3)序列化組件,將對象調(diào)用序列化成二進制字節(jié)流,可理解為一個待發(fā)送的包packet1

4)下游收發(fā)隊列,將報文放入“待發(fā)送隊列”,此時調(diào)用返回,不會阻塞工作線程

5)下游收發(fā)線程,將報文從“待發(fā)送隊列”中取出,通過連接池組件拿到一個可用的連接connection

6)通過連接connection將包packet1發(fā)送給RPC-server

7)發(fā)送包在網(wǎng)絡(luò)傳輸,發(fā)給RPC-server

8)響應(yīng)包在網(wǎng)絡(luò)傳輸,發(fā)回給RPC-client

9)通過連接connection從RPC-server收取響應(yīng)包packet2

10)下游收發(fā)線程,將報文放入“已接受隊列”,通過連接池組件,將conneciont放回連接池

11)下游收發(fā)隊列里,報文被取出,此時回調(diào)將要開始,不會阻塞工作線程

12)序列化組件,將packet2范序列化為Result對象

13)上下文管理器,將結(jié)果,回調(diào),上下文取出

14)通過callback回調(diào)業(yè)務(wù)代碼,返回Result結(jié)果,工作線程繼續(xù)往下走


如果請求長時間不返回,處理流程是:

15)上下文管理器,請求長時間沒有返回

16)超時管理器拿到超時的上下文

17)通過timeout_cb回調(diào)業(yè)務(wù)代碼,工作線程繼續(xù)往下走


上下文管理器

為什么需要上下文管理器?

回答:由于請求包的發(fā)送,響應(yīng)包的回調(diào)都是異步的,甚至不在同一個工作線程中完成,需要一個組件來記錄一個請求的上下文,把請求-響應(yīng)-回調(diào)等一些信息匹配起來。


如何將請求-響應(yīng)-回調(diào)這些信息匹配起來?

這是一個很有意思的問題,通過一條連接往下游服務(wù)發(fā)送了a,b,c三個請求包,異步的收到了x,y,z三個響應(yīng)包:

響應(yīng)包匹配

(1)怎么知道哪個請求包與哪個響應(yīng)包對應(yīng)?

(2)怎么知道哪個響應(yīng)包與哪個回調(diào)函數(shù)對應(yīng)?

回答:這是通過【請求id】來實現(xiàn)請求-響應(yīng)-回調(diào)的串聯(lián)的。

請求id

整個處理流程如上,通過請求id,上下文管理器來對應(yīng)請求-響應(yīng)-callback之間的映射關(guān)系:

1)生成請求id

2)生成請求上下文context,上下文中包含發(fā)送時間time,回調(diào)函數(shù)callback等信息

3)上下文管理器記錄req-id與上下文context的映射關(guān)系,

4)將req-id打在請求包里發(fā)給RPC-server

5)RPC-server將req-id打在響應(yīng)包里返回

6)由響應(yīng)包中的req-id,通過上下文管理器找到原來的上下文context

7)從上下文context中拿到回調(diào)函數(shù)callback

8)callback將Result帶回,推動業(yè)務(wù)的進一步執(zhí)行


如何實現(xiàn)負載均衡,故障轉(zhuǎn)移?

回答:與同步的連接池思路相同。不同在于,同步連接池使用阻塞方式收發(fā),需要與一個服務(wù)的一個ip建立多條連接,異步收發(fā),一個服務(wù)的一個ip只需要建立少量的連接(例如,一條tcp連接)。


如何實現(xiàn)超時發(fā)送與接收?

回答:同步阻塞發(fā)送,可以直接使用帶超時的send/recv來實現(xiàn),異步非阻塞的nio的網(wǎng)絡(luò)報文收發(fā),如何實現(xiàn)超時接收呢?(由于連接不會一直等待回包,那如何知曉超時呢?)這時,超時管理器就上場啦。


超時管理器
超時管理器

超時管理器,用于實現(xiàn)請求回包超時回調(diào)處理。

每一個請求發(fā)送給下游RPC-server,會在上下文管理器中保存req-id與上下文的信息,上下文中保存了請求很多相關(guān)信息,例如req-id,回包回調(diào),超時回調(diào),發(fā)送時間等。

超時管理器啟動timer對上下文管理器中的context進行掃描,看上下文中請求發(fā)送時間是否過長,如果過長,就不再等待回包,直接超時回調(diào),推動業(yè)務(wù)流程繼續(xù)往下走,并將上下文刪除掉。

如果超時回調(diào)執(zhí)行后,正常的回包又到達,通過req-id在上下文管理器里找不到上下文,就直接將請求丟棄(因為已經(jīng)超時處理過了)。


however,異步回調(diào)和同步回調(diào)相比,除了序列化組件和連接池組件,會多出上下文管理器,超時管理器,下游收發(fā)隊列,下游收發(fā)線程等組件,并且對調(diào)用方的調(diào)用習慣有影響(同步->回調(diào))。異步回調(diào)能提高系統(tǒng)整體的吞吐量,具體使用哪種方式實現(xiàn)RPC-client,可以結(jié)合業(yè)務(wù)場景來選?。▽r延敏感的可以選用同步,對吞吐量敏感的可以選用異步)。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號