RPC - 麻雀雖小,五臟俱全

渡碼發表於2019-06-24

說起 RPC (遠端過程呼叫),大家應該不陌生。隨著微服務、分散式越來越流行,RPC 應用越來越普遍。常見的 RPC 框架如:Dubbo、gRPC、Thrift 等。本篇文章不是介紹各種 RPC 的使用和對比。而是深入剖析一個 RPC 包含哪些內容。我最近在 Hadoop 的原始碼,正好把 Hadoop RPC 看完了。感覺 Hadoop 的 RPC 框架設計的還是比價優秀的。Hadoop 作為大資料技術的基石,如果沒有一個高效能、高可靠的 RPC 框架,很難支撐上千臺伺服器規模的叢集。因此,本篇文章就以 Hadoop RPC 為例,介紹一個 RPC 框架會涉及的技術。

架構設計

RPC 的架構涉及客戶端、網路、服務端三大元件。網路一般使用 socket ,更多的是基於現有的網路框架進行引數的設定達到最優的目的。但是客戶端和服務端需要我們自己設計,並且對於分散式框架來說,設計的架構應該有高效能、高可用以及可擴充套件的特點。

  • 高效能:由於客戶端同時發起多個請求,這就要求系統能夠快速處理,降低響應延遲。也就是高吞吐、低延遲。從客戶端角度來說,由於建立客戶端到服務端的連線成本較高。因此可以快取連線資源,從而實現多個客戶端複用相同的連線資源,避免每個客戶端都來建立而降低效能;從服務端角度來說,可以啟動多執行緒來併發處理客戶端請求。除了多執行緒,可以採用 Reactor 程式設計模式,提高多執行緒併發的效能。
  • 高可用:當我們的服務端掛了,能不能有備用節點繼續提供服務。Hadoop 2.x 實現了 NameNode 的高可用。當客戶端需要通過 RPC 呼叫 NameNode 服務的過程中,如果主 NameNode 當機,那麼備用 NameNode 會升級成活動節點。同時會將 RPC 的請求傳送的當前活躍的 NameNode,從而繼續提供可用的服務,而這個過程對客戶端來說是透明的。
  • 可擴充套件性:一個框架需要不斷地優化、不斷升級。需要在架構設計時明確不變的需求點,以及可變的需求點,對於可變的需求需要能夠有良好的可擴充套件性。以 RPC 涉及的序列化為例。由於不同序列化框架適用場景不同,因此這需要被當成可變的需求點,應該將其設計成可擴充套件的,能夠容易地支援不同的序列化框架。目前,Hadoop RPC 支援自身的序列化框架(Writable)和 Protoc Buffer。

設計模式

設計模式更多地與上面提到的可擴充套件性相呼應。良好的設計模式可以提高程式碼複用性、增強可擴充套件性,同時能夠降低 BUG 數量。Hadoop RPC 中涉及的設計模式比較多,大概包括:工廠模式、代理模式、介面卡模式、裝飾者模式和命令模式等。以代理模式為例,當客戶端呼叫遠端方法時,實際上是通過代理,將方法名和引數通過網路傳送到服務端。但這個過程對客戶端是透明的,對於客戶端來說就像呼叫本地方法一樣。

除了設計模式,在工程實踐中還應該注意遵循常見的設計原則。

多執行緒

在任何一個系統中多執行緒都比較常見。通過多執行緒併發處理,提高系統的吞吐量。在 Hadoop RPC 中,客戶端與服務端都用到了多執行緒技術。客戶端開啟多執行緒,每個執行緒處理一類請求,並且快取連線資源。服務端也是多執行緒併發處理客戶端的請求,使用 Reactor 程式設計模式提高併發效能。

談到多執行緒就不得不提另一個話題 —— 執行緒安全。Hadoop RPC 中用了不少的技術來保證執行緒安全,包括:synchronized、concurrent併發包、atomic併發包和 nio 工具包。從優秀框架中學習執行緒安全,對我們以後併發程式設計有不少好處。

序列化與反序列化

由於 RPC 涉及資料在網路上傳輸,因此需要一個優秀的序列化框架,既能夠高效的編碼與解碼,且編碼後的資料大小又儘可能小。不同的序列化框架主要是在編解碼效率和編碼大小兩個主要方面做權衡。Hadoop RPC 目前支援兩種序列化框架,一個是 Hadoop 自己實現的 Writable 框架,另一個是 Protocol Buffer。Hadoop RPC 雖然支援 Writable 序列化框架,但還是以 Protocol Buffer 為主。因為 Protocol Buffer 從編解碼效率和編碼大小方便都是比較優秀的。當然常見的序列化包括 Avro、Kryo 等,有興趣的讀者可以查一下它們之間的效能對比。

其他

一個 RPC 框架,除了包含上面提到比較主要的方面。還有一些其他的方面

  • 語言層面:利用好 Java 語言的繼承、組合、封裝、多型等特性。甚至包括泛型、註解等。
  • 程式碼規範:良好的工程實現應該有一個良好的程式碼規範。在 Hadoop 中,程式碼風格比較統一,且每個重要的類都有詳細的註釋,在關鍵的方法或者屬性上也有明確的註釋。我在自己的工程中會使用阿里的 Java 程式碼規約外掛,也會為了讓自己的程式碼更規範。
  • 異常處理:對於一個優秀的框架異常處理很關鍵,什麼時候需要丟擲異常、丟擲什麼樣的異常以及什麼時候需要處理異常。在 RPC 中除了需要處理本地異常還要處理遠端服務的異常。因此,在程式中如何優雅的處理異常也是體現一個程式設計師能力的地方。
  • 網路程式設計:RCP 中涉及的網路程式設計一般用 socket,Hadoop RPC 使用的 Reactor 模式的網路程式設計,並且 Netty 也在使用這種框架。我們有必要會用並且掌握它。

 這一段寫的比較雜,想到哪寫到哪。最近有跟朋友聊過在看 RPC 相關的東西,朋友說:“一個 RPC 能夠涉及多少東西?值得研究?”。其實我一開始也是這樣想的,無非就是客戶端將請求序列化,通過網路發給服務端,服務端反序列化呼叫函式後再返回。但是看了 Hadoop RPC 程式碼後,我發現這樣框架涉及的知識還是特別多的,並且還比較系統,基本上包含了我們平時程式設計涉及的方方面面。同時它不再是一個單機程式,而是一個 C/S 架構的程式。如果我們有興趣還可以繼續研究他的高可用,從而對分散式應用有更深入的瞭解。

我覺得 RPC 是麻雀雖小五臟俱全。由於它涉及了我們程式設計的方方面面,所以我想基於 Hadoop RPC 做一個詳細的教程,把它涉及的每個重要部分都進行詳細的分析,上面提到的內容基本都會涵蓋。對於想了解 RPC 的讀者,能夠感受到一個 RPC 框架更清晰的面貌。對於僅有 Java 基礎的讀者來說,能夠學到編寫一個框架所涉及的具體程式設計技術,同時能夠從世界頂級開源專案學到優秀設計和工程經驗。

小結 

本篇文章主要介紹了 RPC 框架涉及的知識。包括:架構設計、設計模式以及設計原則、多執行緒併發以及執行緒安全、序列化框架和一些其他的內容。我覺得學習最好的方式就是從優秀的框架中學習、模仿。好比我們練書法基本都要經過臨摹這一步。當然直接看別人的程式碼確實需求花費更多的時間和經歷,並且有時候投入與產出並不成正比。所以,我想把我在 Hadoop RPC 框架中學到的優秀的設計和實現能夠整理成教程,以便有興趣的讀者學習。如果有任何建議歡迎與我交流。公眾號有福利

公眾號「渡碼」

 

相關文章