RPC(Remote Procedure Call)框架詳解

RPC(Remote Procedure Call)框架

RPC(Remote Procedure Call):遠程過程調用,它是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的思想。

RPC 是一種技術思想而非一種規範或協議,常見 RPC 技術和框架有:

應用級的服務框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud。
遠程通信協議:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
通信框架:MINA 和 Netty。
目前流行的開源 RPC 框架還是比較多的,有阿里巴巴的 Dubbo、Facebook 的 Thrift、Google 的 gRPC、Twitter 的 Finagle 等。

下面重點介紹三種:

  • gRPC:是 Google 公佈的開源軟件,基於最新的 HTTP 2.0 協議,並支持常見的衆多編程語言。RPC 框架是基於 HTTP 協議實現的,底層使用到了 Netty 框架的支持。
  • Thrift:是 Facebook 的開源 RPC 框架,主要是一個跨語言的服務開發框架。 用戶只要在其之上進行二次開發就行,應用對於底層的 RPC
    通訊等都是透明的。不過這個對於用戶來說需要學習特定領域語言這個特性,還是有一定成本的。
  • Dubbo:是阿里集團開源的一個極爲出名的 RPC 框架,在很多互聯網公司和企業應用中廣泛使用。協議和序列化框架都可以插拔是極其鮮明的特色。
完整的 RPC 框架

在一個典型 RPC 的使用場景中,包含了服務發現、負載、容錯、網絡傳輸、序列化等組件,其中“RPC 協議”就指明瞭程序如何進行網絡傳輸和序列化。
在這裏插入圖片描述
在這裏插入圖片描述
如下是 Dubbo 的設計架構圖,分層清晰,功能複雜:
在這裏插入圖片描述

RPC 核心功能

RPC 的核心功能是指實現一個 RPC 最重要的功能模塊,就是上圖中的”RPC 協議”部分:
在這裏插入圖片描述
一個 RPC 的核心功能主要有 5 個部分組成,分別是:客戶端、客戶端 Stub、網絡傳輸模塊、服務端 Stub、服務端等。
在這裏插入圖片描述

下面分別介紹核心 RPC 框架的重要組成:
  • 客戶端(Client):服務調用方。
  • 客戶端存根(Client Stub):存放服務端地址信息,將客戶端的請求參數數據信息打包成網絡消息,再通過網絡傳輸發送給服務端。
  • 服務端存根(Server Stub):接收客戶端發送過來的請求消息並進行解包,然後再調用本地服務進行處理。
  • 服務端(Server):服務的真正提供者。
  • Network Service:底層傳輸,可以是 TCP 或 HTTP。
一次 RPC 調用流程如下:
  • 服務消費者(Client 客戶端)通過本地調用的方式調用服務。
  • 客戶端存根(Client Stub)接收到調用請求後負責將方法、入參等信息序列化(組裝)成能夠進行網絡傳輸的消息體。
  • 客戶端存根(Client Stub)找到遠程的服務地址,並且將消息通過網絡發送給服務端。
  • 服務端存根(Server Stub)收到消息後進行解碼(反序列化操作)。
  • 服務端存根(Server Stub)根據解碼結果調用本地的服務進行相關處理
  • 服務端(Server)本地服務業務處理。
  • 處理結果返回給服務端存根(Server Stub)。
  • 服務端存根(Server Stub)序列化結果。
  • 服務端存根(Server Stub)將結果通過網絡發送至消費方。
  • 客戶端存根(Client Stub)接收到消息,並進行解碼(反序列化)。
  • 服務消費方得到最終結果。
    在這裏插入圖片描述
RPC 核心之功能實現

RPC 的核心功能主要由 5 個模塊組成,如果想要自己實現一個 RPC,最簡單的方式要實現三個技術點,分別是:

  • 服務尋址
  • 數據流的序列化和反序列化
  • 網絡傳輸

服務尋址

服務尋址可以使用 Call ID 映射。在本地調用中,函數體是直接通過函數指針來指定的,但是在遠程調用中,函數指針是不行的,因爲兩個進程的地址空間是完全不一樣的。

所以在 RPC 中,所有的函數都必須有自己的一個 ID。這個 ID 在所有進程中都是唯一確定的。

客戶端在做遠程過程調用時,必須附上這個 ID。然後我們還需要在客戶端和服務端分別維護一個函數和Call ID的對應表。

當客戶端需要進行遠程調用時,它就查一下這個表,找出相應的 Call ID,然後把它傳給服務端,服務端也通過查表,來確定客戶端需要調用的函數,然後執行相應函數的代碼。

實現方式:服務註冊中心。

要調用服務,首先你需要一個服務註冊中心去查詢對方服務都有哪些實例。Dubbo 的服務註冊中心是可以配置的,官方推薦使用 Zookeeper。

實現案例:RMI(Remote Method Invocation,遠程方法調用)也就是 RPC 本身的實現方式。
在這裏插入圖片描述
Registry(服務發現):藉助 JNDI 發佈並調用了 RMI服務。實際上,JNDI 就是一個註冊表,服務端將服務對象放入到註冊表中,客戶端從註冊表中獲取服務對象。

RMI 服務在服務端實現之後需要註冊到 RMI Server 上,然後客戶端從指定的 RMI 地址上 Lookup 服務,調用該服務對應的方法即可完成遠程方法調用。

Registry 是個很重要的功能,當服務端開發完服務之後,要對外暴露,如果沒有服務註冊,則客戶端是無從調用的,即使服務端的服務就在那裏。

序列化和反序列化:客戶端怎麼把參數值傳給遠程的函數呢?在本地調用中,我們只需要把參數壓到棧裏,然後讓函數自己去棧裏讀就行。

但是在遠程過程調用時,客戶端跟服務端是不同的進程,不能通過內存來傳遞參數。

這時候就需要客戶端把參數先轉成一個字節流,傳給服務端後,再把字節流轉成自己能讀取的格式。

  • 將對象轉換成二進制流的過程叫做反序列化
  • 將二進制流轉換成對象的過程叫做反序列化

這個過程叫序列化和反序列化。同理,從服務端返回的值也需要序列化反序列化的過程。

網絡傳輸:遠程調用往往用在網絡上,客戶端和服務端是通過網絡連接的。

所有的數據都需要通過網絡傳輸,因此就需要有一個網絡傳輸層。網絡傳輸層需要把 Call ID 和序列化後的參數字節流傳給服務端,然後再把序列化後的調用結果傳回客戶端。

只要能完成這兩者的,都可以作爲傳輸層使用。因此,它所使用的協議其實是不限的,能完成傳輸就行。

儘管大部分 RPC 框架都使用 TCP 協議,但其實 UDP 也可以,而 gRPC 乾脆就用了 HTTP2。

TCP 的連接是最常見的,簡要分析基於 TCP 的連接:通常 TCP 連接可以是按需連接(需要調用的時候就先建立連接,調用結束後就立馬斷掉),也可以是長連接(客戶端和服務器建立起連接之後保持長期持有,不管此時有無數據包的發送,可以配合心跳檢測機制定期檢測建立的連接是否存活有效),多個遠程過程調用共享同一個連接。

所以,要實現一個 RPC 框架,只需要把以下三點實現了就基本完成了:

  • Call ID 映射:可以直接使用函數字符串,也可以使用整數 ID。映射表一般就是一個哈希表。
  • 序列化反序列化:可以自己寫,也可以使用 Protobuf 或者 FlatBuffers 之類的。
  • 網絡傳輸庫:可以自己寫 Socket,或者用 Asio,ZeroMQ,Netty 之類。
RPC 核心之網絡傳輸協議

在第三節中說明了要實現一個 RPC,需要選擇網絡傳輸的方式。
在這裏插入圖片描述
在 RPC 中可選的網絡傳輸方式有多種,可以選擇 TCP 協議、UDP 協議、HTTP 協議。

每一種協議對整體的性能和效率都有不同的影響,如何選擇一個正確的網絡傳輸協議呢?首先要搞明白各種傳輸協議在 RPC 中的工作方式。

基於 TCP 協議的 RPC 調用

由服務的調用方與服務的提供方建立 Socket 連接,並由服務的調用方通過 Socket 將需要調用的接口名稱、方法名稱和參數序列化後傳遞給服務的提供方,服務的提供方反序列化後再利用反射調用相關的方法。

最後將結果返回給服務的調用方,整個基於 TCP 協議的 RPC 調用大致如此。

但是在實例應用中則會進行一系列的封裝,如 RMI 便是在 TCP 協議上傳遞可序列化的 Java 對象。

基於 HTTP 協議的 RPC 調用

該方法更像是訪問網頁一樣,只是它的返回結果更加單一簡單。

其大致流程爲:由服務的調用者向服務的提供者發送請求,這種請求的方式可能是 GET、POST、PUT、DELETE 等中的一種,服務的提供者可能會根據不同的請求方式做出不同的處理,或者某個方法只允許某種請求方式。

而調用的具體方法則是根據 URL 進行方法調用,而方法所需要的參數可能是對服務調用方傳輸過去的 XML 數據或者 JSON 數據解析後的結果,最後返回 JOSN 或者 XML 的數據結果。

由於目前有很多開源的 Web 服務器,如 Tomcat,所以其實現起來更加容易,就像做 Web 項目一樣。

兩種方式對比

基於 TCP 的協議實現的 RPC 調用,由於 TCP 協議處於協議棧的下層,能夠更加靈活地對協議字段進行定製,減少網絡開銷,提高性能,實現更大的吞吐量和併發數。

但是需要更多關注底層複雜的細節,實現的代價更高。同時對不同平臺,如安卓,iOS 等,需要重新開發出不同的工具包來進行請求發送和相應解析,工作量大,難以快速響應和滿足用戶需求。

基於 HTTP 協議實現的 RPC 則可以使用 JSON 和 XML 格式的請求或響應數據。

而 JSON 和 XML 作爲通用的格式標準(使用 HTTP 協議也需要序列化和反序列化,不過這不是該協議下關心的內容,成熟的 Web 程序已經做好了序列化內容),開源的解析工具已經相當成熟,在其上進行二次開發會非常便捷和簡單。

但是由於 HTTP 協議是上層協議,發送包含同等內容的信息,使用 HTTP 協議傳輸所佔用的字節數會比使用 TCP 協議傳輸所佔用的字節數更高。

因此在同等網絡下,通過 HTTP 協議傳輸相同內容,效率會比基於 TCP 協議的數據效率要低,信息傳輸所佔用的時間也會更長,當然壓縮數據,能夠縮小這一差距。

使用 RabbitMQ 的 RPC 架構

在 OpenStack 中服務與服務之間使用 RESTful API 調用,而在服務內部則使用 RPC 調用各個功能模塊。

正是由於使用了 RPC 來解耦服務內部功能模塊,使得 OpenStack 的服務擁有擴展性強,耦合性低等優點。

OpenStack 的 RPC 架構中,加入了消息隊列 RabbitMQ,這樣做的目的是爲了保證 RPC 在消息傳遞過程中的安全性和穩定性。

下面分析 OpenStack 中使用 RabbitMQ 如何實現 RPC 的調用。
在這裏插入圖片描述
使用 RabbitMQ 的好處

  • 同步變異步:可以使用線程池將同步變成異步,但是缺點是要自己實現線程池,並且強耦合。使用消息隊列可以輕鬆將同步請求變成異步請求。
  • 低內聚高耦合:解耦,減少強依賴。
  • 流量削峯:通過消息隊列設置請求最大值,超過閥值的拋棄或者轉到錯誤界面。
  • 網絡通信性能提高:TCP 的創建和銷燬開銷大,創建 3 次握手,銷燬 4
    次分手,高峯時成千上萬條的鏈接會造成資源的巨大浪費,而且操作系統每秒處理 TCP 的數量也是有數量限制的,必定造成性能瓶頸。
    RabbitMQ 採用信道通信,不採用 TCP 直接通信。一條線程一條信道,多條線程多條信道,公用一個 TCP 連接。 一條 TCP
    連接可以容納無限條信道(硬盤容量足夠的話),不會造成性能瓶頸。

原文作者:李金葵
原文鏈接:https://www.sohu.com/a/320599994_463994

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