【58沈劍架構系列】RPC-client異步收發核心細節?

第一章聊了【“爲什麼要進行服務化,服務化究竟解決什麼問題”

第二章聊了【“微服務的服務粒度選型”

第三章聊了【“爲什麼說要搞定微服務架構,先搞定RPC框架?”

上一章聊了【“微服務架構之RPC-client序列化細節”

 

通過上篇文章的介紹,知道了要實施微服務,首先要搞定RPC框架,RPC框架分爲客戶端部分與服務端部分。


RPC-client的部分又分爲:

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

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

前一篇文章討論了序列化與範序列化的細節,這一篇文章將討論發送字節流與接收字節流的部分。

 

客戶端調用又分爲同步調用與異步調用

同步調用的代碼片段爲:

Result = Add(Obj1, Obj2);// 得到Result之前處於阻塞狀態

異步調用的代碼片段爲:

Add(Obj1, Obj2, callback);// 調用後直接返回,不等結果

處理結果通過回調得到:

callback(Result){// 得到處理結果後會調用這個回調函數

         …

}

這兩個調用方式,RPC-client裏,處理方式也不一樣,下文逐一敘述。

 

RPC-client同步調用


所謂同步調用,在得到結果之前,一直處於阻塞狀態,會一直佔用一個工作線程,上圖簡單的說明了一下組件、交互、流程步驟。

 

上圖中的左邊大框,就代表了調用方的一個工作線程。

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

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

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

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

1)業務代碼發起RPC調用,Result=Add(Obj1,Obj2)

2)序列化組件,將對象調用序列化成二進制字節流,可理解爲一個待發送的包packet1

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

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

5)發送包在網絡傳輸,發給RPC-server

6)響應包在網絡傳輸,發回給RPC-client

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

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

9)序列化組件,將packet2範序列化爲Result對象返回給調用方

10)業務代碼獲取Result結果,工作線程繼續往下走

 

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

連接池組件


典型連接池組件對外提供的接口爲:

int ConnectionPool::init(…);

Connection ConnectionPool::getConnection();

intConnectionPool::putConnection(Connection t);

【INIT】

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

【getConnection】

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

【putConnection】

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

 

如何實現負載均衡?

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

 

如何實現故障轉移?

回答:連接池中建立了與一個RPC-server集羣的連接,當連接池發現某一個機器的連接異常後,需要將這個機器的連接排除掉,返回正常的連接,在機器恢復後,再將連接加回來。

 

如何實現發送超時?

回答:因爲是同步阻塞調用,拿到一個連接後,使用帶超時的send/recv即可實現帶超時的發送和接收。

 

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

 

RPC-client異步回調


所謂異步回調,在得到結果之前,不會處於阻塞狀態,理論上任何時間都沒有任何線程處於阻塞狀態,因此異步回調的模型,理論上只需要很少的工作線程與服務連接就能夠達到很高的吞吐量。

 

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

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

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

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

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

1)業務代碼發起異步RPC調用,Add(Obj1,Obj2, callback)

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

3)序列化組件,將對象調用序列化成二進制字節流,可理解爲一個待發送的包packet1

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

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

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

7)發送包在網絡傳輸,發給RPC-server

8)響應包在網絡傳輸,發回給RPC-client

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

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

11)下游收發隊列裏,報文被取出,此時回調將要開始,不會阻塞工作線程

12)序列化組件,將packet2範序列化爲Result對象

13)上下文管理器,將結果,回調,上下文取出

14)通過callback回調業務代碼,返回Result結果,工作線程繼續往下走

 

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

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

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

17)通過timeout_cb回調業務代碼,工作線程繼續往下走

 

上下文管理器

爲什麼需要上下文管理器?

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

 

如何將請求-響應-回調這些信息匹配起來?

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


(1)怎麼知道哪個請求包與哪個響應包對應?

(2)怎麼知道哪個響應包與哪個回調函數對應?

回答:這是通過【請求id】來實現請求-響應-回調的串聯的。


整個處理流程如上,通過請求id,上下文管理器來對應請求-響應-callback之間的映射關係:

1)生成請求id

2)生成請求上下文context,上下文中包含發送時間time,回調函數callback等信息

3)上下文管理器記錄req-id與上下文context的映射關係,

4)將req-id打在請求包裏發給RPC-server

5)RPC-server將req-id打在響應包裏返回

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

7)從上下文context中拿到回調函數callback

8)callback將Result帶回,推動業務的進一步執行

 

如何實現負載均衡,故障轉移?

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

 

如何實現超時發送與接收?

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

 

超時管理器


超時管理器,用於實現請求回包超時回調處理。

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

超時管理器啓動timer對上下文管理器中的context進行掃描,看上下文中請求發送時間是否過長,如果過長,就不再等待回包,直接超時回調,推動業務流程繼續往下走,並將上下文刪除掉。

如果超時回調執行後,正常的回包又到達,通過req-id在上下文管理器裏找不到上下文,就直接將請求丟棄(因爲已經超時處理過了)。

 

however,異步回調和同步回調相比,除了序列化組件和連接池組件,會多出上下文管理器,超時管理器,下游收發隊列,下游收發線程等組件,並且對調用方的調用習慣有影響(同步->回調)。異步回調能提高系統整體的吞吐量,具體使用哪種方式實現RPC-client,可以結合業務場景來選取(對時延敏感的可以選用同步,對吞吐量敏感的可以選用異步)。

 

末了,通過最近幾篇RPC框架細節的文章閱讀量來看,貌似大夥對細節不是特別感興趣,後續文章就不再延續這個系列啦。

==【完】==

【文章轉載自微信公衆號“架構師之路”】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章