最近線上碰到一點小問題,分析其原因發現是出在對 RPC 使用上的一些細節掌握不夠清晰導致。很多時候我們做業務開發會把 RPC 當作黑盒機制來使用,但若不對黑盒的工作原理有個基本掌握,也容易犯一些誤用的微妙錯誤。 雖然曾經已經寫過一篇 "《RPC 的概念模型與實現解析》" 從概念模型和實現細節上講述了 ...
最近線上碰到一點小問題,分析其原因發現是出在對 RPC 使用上的一些細節掌握不夠清晰導致。很多時候我們做業務開發會把 RPC 當作黑盒機制來使用,但若不對黑盒的工作原理有個基本掌握,也容易犯一些誤用的微妙錯誤。
雖然曾經已經寫過一篇《RPC 的概念模型與實現解析》 從概念模型和實現細節上講述了 RPC 的原理,這一篇就從使用上的一些註意點來捋一捋吧。
同步
RPC 的調用通常為了方便使用,會被偽裝成普通方法調用的形式。但實際二者之間存在巨大的差異,進程內的方法調用的時間量級是 ns(納秒),而進程間的 RPC 方法調用時間量級通常是 ms(毫秒),它們之間差著 10 的六次方呢。RPC 的冰山底部透視圖如下:
但在目前流行的微服務架構模式下,跨服務的同步調用隱藏著巨大的風險。一般微服務化架構下,通常一個業務的調用會跨 N(N 一般大於 2) 個服務進程,整個調用鏈路上的同步調用等待的瓶頸會由最慢(或脆弱)的服務決定,A-B-C 像這樣一個鏈路,A 同步調用 B 並等待返回,B 同步調用 C 並等待返回,以此類推,就像一組齒輪鏈,級級傳動,這很容易產生雪崩效應。若 C 服務掛住了,會導致前面的服務全部都因為等待超時而占用大量不必要的線程資源。
因此,微服務架構下,內部主服務鏈之間的 RPC 調用需要非同步化,服務之間的調用請求和等待結果相互之間解耦,如下是一個服務鏈路調用的示意圖:
外部用戶通過服務網關(API Gateway)發起調用並等待結果,隨後網關派發調用請求給後續服務,其主調用鏈路為 A-B-C,其內部為非同步調用,鏈路上不等待,最後由 C 返回結果給服務網關。其中 B 又依賴兩個子服務,S1 和 S2,B 需要 S1 和 S2 的返回結果才能發起 C 調用,因此在支線上 B 針對 S1 和 S2 調用就需要是同步的。
非同步
RPC 的同步調用確保請求送達對方並收到對方響應,若沒有收到響應,框架則拋出 Timeout 異常。這種情況下調用方是無法確定調用是成功還是失敗的,需要根據業務場景(是否可重入,冪等)選擇重試和補償策略。
而 RPC 的非同步調用意味著 RPC 框架不阻塞調用方線程,調用方不需要立刻拿到返回結果,甚至調用方根本就不關心返回結果。RPC 的非同步交互場景示意圖如下:
在上面的示意圖中,對於是否需要返回值的非同步請求,其中的細微差異在於是否返回一個 Future 對象給調用方,以便未來(Future)調用方可以再通過它來獲取返回值。正是因為這種 Future 機制的存在,所以針對前面(圖2)中 S1 和 S2 的調用就可以採用一種非同步並行的調用機制來提升並行性和性能,如下圖所示:
這樣調用 S1 和 S2 的總時間就由最慢的一個服務響應時間來決定了。(上圖中其實調用 S1 和 S2 不可能做到同時,有細微的時間差異,但相對跨進程的調用本身來說這種差異基本忽略不計。)
線程
RPC 的線程模型一般如下所示:
其中,RPC 的網路層通常採用非阻塞型 I/O 模型,放在 Java 的實現語境下就是 NIO 了。而 RPC 框架通常共用一個 I/O 線程池,處理所有連接上的 I/O 事件派發。通常業務事件會派發到內部的一個固定大小(可配置)的業務執行線程池,再由業務執行線程調用應用實現層的代碼。
但有些 RPC 框架在實現客戶端的 I/O 線程模型時,也採用了針對每個不同的服務端一個獨立的 I/O 線程池,這樣就變成了下麵這個圖所示:
這帶來了一個潛在的問題,在一個客戶端需要連接大量服務端時(這在基於 RPC 實現的服務框架中很常見),客戶端的 I/O 線程池數就和需連接的服務數相等。在現在的微服務部署模式下,一般一個服務部署在一個 Docker 容器中,同一個服務會有很多個(幾十上百個)進程共同組成集群提供服務,這樣就導致客戶端 I/O 線程數可能會很多。
而在 Docker 環境下 Java 的 Runtime.availableProcessors() 獲取的 CPU 數量實際是物理機的,而不是 Docker 隔離的核數。另外,像 Netty 這樣的網路框架經常預設是基於 CPU 核數來啟動預設的 I/O 線程數的,所以導致針對每個服務的客戶端會啟動 CPU 核數個 I/O 線程再乘上服務實例數,這個線程數量也是頗為客觀,出現單進程好幾千固化的線程,線程調度和切換的成本頗高,另外服務的水平擴展性也有一定的受限。這也是需要註意的另一點。
...
在曾經那篇《RPC 的概念模型與實現解析》 的的結尾,我曾寫到:
無論 RPC 的概念是如何優雅,但是“草叢中依然有幾條蛇隱藏著”,只有深刻理解了 RPC 的本質,才能更好地應用。
所以這一篇大概就是抓出了幾條隱藏著的蛇吧。
寫點文字,畫點畫兒,記錄成長瞬間。
微信公眾號「瞬息之間」,既然遇見,不如一起成長。