從本文開始,將記錄作者學習 MIT 6.824 分散式系統的學習筆記,如果有志同道合者,歡迎一起交流。
RPC 的定義和結構
RPC 全稱為 Remote Procedure Call,他表示一種遠端過程的呼叫,他可以讓使用者在不知道底層網路協議的情況下,在本地的計算機就可以呼叫遠端伺服器的一些處理方法,然後伺服器會把處理的結果返回到使用者本地,就像這個方法寫在使用者本地空間中一樣。
RPC 的層級結構如下所示:
- Client 端(由上到下):
- Application:應用層,表示和使用者直接互動的部分,使用者在該層確定自己想要呼叫的函式,並且輸入引數
- stub:原意是菸蒂,很形象的表示這只是個函式空殼,表示使用者想要呼叫的函式,這裡可以是函式名
- RPC lib:RPC 庫,包括一系列的編碼解碼工作,對於使用者端來說,是編碼使用者的函式呼叫請求進入網路層,或者是解碼服務端的結果成為上層結構可以理解的語言
- Network Layer:網路層傳輸協議,這裡不再贅述,它可以使 TCP
- Server 端(由下到上):
- Network Layer:網路層傳輸協議,同上
- RPC lib:RPC 庫,對服務端來說,用於解碼使用者的呼叫函式請求,或者是函式結果的封裝。
- Dispatcher:使用者的請求到達該層後,服務端需要通過 Dispatcher,根據請求的函式,找到伺服器中對應的函式,並把任務交到這個函式上
- Handler:具體的函式邏輯
如上圖所示,RPC 的操作流程為:
- 使用者端寫好 function call,向下傳遞到網路層併傳送到服務端
- 服務端上行使用者請求,通過 Dispatcher 找到具體的 Handler,由 Handler 處理具體的邏輯
- 服務端處理完畢後,通過 RPC lib 編碼發還到客戶端
- 客戶端經過解碼後上行結果,使用者獲得 Application 層獲得結果返回。
RPC 的關鍵技術點
RPC 具體實現的關鍵技術點如下
- Marshall / Unmarshall:即為我們平常說的序列化/反序列化,用於將應用層的資訊轉化為網路層可以理解語言,抑或是將網路層的資訊解碼成為應用層可以理解的語言。這部分在 RPC lib 中可以完成
- Binding:在一個大的網路環境中,如何為使用者分配一個符合使用者所需邏輯要求的,可用的伺服器也是一大難點
- Threads:handler 可能執行得很慢,而通過多執行緒則可以增強 Server 的吞吐量
RPC 的潛在失敗情況和處理方案
RPC 的潛在失敗情況有如下幾種:
- Lose Packet:網路環境差造成的丟包
- Broken Net:網路直接斷了
- Server Crash:伺服器崩了
- Server Slow:伺服器處理速度太慢
對此,為了一定要讓客戶端拿到處理結果,有以下幾種解決方案:
方案一:At Least once:
該方法的中心思想是:client 傳送等待 server 回答,如果沒收到,則繼續傳送。該方式可能造成的問題有:
-
問題一:可能客戶端 -> 服務端的網路一直相同,伺服器處理正常,但是在回覆過程出現問題,從而造成客戶端持續傳送請求,使得客戶端一直重複處理,浪費資源。
-
問題二:如果要處理的請求具有事務性,如運算元據庫,那麼使用這種方案就無法保證前後的一致性。考慮如下的情況,假設我們要操作的物件是服務端的資料庫,可能會出現如下的問題:
如上圖所示,PUT 表示我們要去修改一個 key 對應的值,而 GET 則讓我們獲取這個 key 對應的值,因此當我們延遲的請求到達伺服器時,伺服器已經處理其他的請求,造成我們最後一次 GET 方法獲取的結果可能與我們的預期不同。
所以 At least once 會造成資源浪費,服務端出現不一致的情況,不是一個很好的方法
方案二:At Most Once
其實,方案一的問題在於服務端無法判斷客戶端因為各種原因收不到而重複發出的請求,因此我們需要讓伺服器可以發現之前的重複請求,並且在不執行 handler 的情況下,返回之前快取的結果。因此我們的請求需要帶上唯一的識別碼,這個識別碼可以通過 client ip + 時間戳 生成,也可以使用 client ip + seq(序列號)。這樣,服務端就可以知道這是不是一個已經處理過的請求,並通過快取直接返回結果。
然而,這對服務端也提出了新的問題:
-
問題一: 什麼時候可以刪掉這些快取?
畢竟服務端不可能永遠儲存這些快取,這會造成容量不夠的問題。答案其實也很簡單,那就是客戶端證明自己收到了回覆的時候。對此,我們 RPC 使用者端協議可以帶一些附加資訊,比如:
-
對於客戶端 RPC 請求,可以帶上其近期已經收到回覆的序列號,這樣,服務端的在解碼過程中可以通過解析該欄位,從而刪除已經確定收到回覆的快取
-
可以通過使用 sequence number 的方法,即客戶端的該次請求是上一次請求的序列號 + 1。這樣,客戶端就可以在請求中帶上一個序列號 n,表示 seq < n 的請求我都已經收到了。服務端即可刪除所有序列號小於 n 的快取。如下的示意圖展示了這一種方法:
-
-
問題二:如果一個請求仍在處理,此時"心急的"客戶端又發了一個相同的請求,該如何處理?
這個時候,服務端可以維護一個關於"處理中"的快取,將正在處理的 request 記錄在該快取,如果有相同請求到來,則先去查詢"處理中"的快取,再去查詢"已完成"的快取,如果存在,則捨棄這條請求,返回已有結果或者等待原有的相同請求處理完成並返回。
總結
本章是對 MIT 6.824 第二課 RPC 內容的總結,主要講了 RPC 的結構,以及一些潛在錯誤的處理方法,為 lab1 中 mapreduce RPC 的實現打下基礎。