RPC(Remote Procedure Call):遠程過程調用,它是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的思想。
RPC 是一種技術思想而非一種規范或協議,常見 RPC 技術和框架有:
- 應用級的服務框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud、Facebook 的 Thrift、Twitter 的 Finagle 等。
- 遠程通信協議:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
- 通信框架:MINA 和 Netty。
- ps: Google gRPC 框架是基於 HTTP2 協議實現的,底層使用到了 Netty 框架的支持。
1. RPC 框架
一個典型 RPC 的使用場景中,包含了服務發現、負載、容錯、網絡傳輸、序列化等組件,其中“RPC 協議”就指明了程序如何進行網絡傳輸和序列化。
圖 1:完整 RPC 架構圖
2. RPC 核心功能
一個 RPC 的核心功能主要有 5 個部分組成,分別是:客戶端、客戶端 Stub、網絡傳輸模塊、服務端 Stub、服務端等。
圖 4:RPC 核心功能圖
下面分別介紹核心 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的目標就是要2~10這些步驟都封裝起來,讓用戶對這些細節透明。
3. RPC 核心功能實現技術點
- 透明化遠程服務調用:字節碼生成,JDK動態代理
- 編碼與解碼
- 服務尋址:Call ID 映射,可以直接使用函數字符串,也可以使用整數 ID。映射表一般就是一個哈希表。
- 數據流的序列化和反序列化:可以自己寫,也可以使用 Protobuf 或者 FlatBuffers 之類的。
- 網絡傳輸:可以自己寫 Socket,或者用 Asio,ZeroMQ,Netty 之類。在 RPC 中可選的網絡傳輸方式有多種,可以選擇 TCP 協議、UDP 協議、HTTP 協議
3.1 透明化遠程服務調用(代理)
- jdk 動態代理:更多使用動態代理
- 字節碼生成:更為強大和高效,但代碼維護不易
3.2 服務尋址(服務注冊中心)
實現方式:服務注冊中心。
Call ID 映射
/** 1) 服務尋址可以使用 Call ID 映射。在本地調用中,函數體是直接通過函數指針來指定的,但是在遠程調用中,函數指針是不行的,因為兩個進程的地址空間是完全不一樣的。 2) 所以在 RPC 中,所有的函數都必須有自己的一個 ID。這個 ID 在所有進程中都是唯一確定的。 3) 客戶端在做遠程過程調用時,必須附上這個 ID。然后我們還需要在客戶端和服務端分別維護一個函數和Call ID的對應表。 4) 當客戶端需要進行遠程調用時,它就查一下這個表,找出相應的 Call ID,然后把它傳給服務端,服務端也通過查表,來確定客戶端需要調用的函數,然后執行相應函數的代碼。 **/
實現案例:RMI(Remote Method Invocation,遠程方法調用)也就是 RPC 本身的實現方式。
圖 9:RMI 架構圖
Registry(服務發現):借助 JNDI 發布並調用了 RMI 服務。實際上,JNDI 就是一個注冊表,服務端將服務對象放入到注冊表中,客戶端從注冊表中獲取服務對象。
3.3 編碼與解碼
客戶端的請求消息結構一般需要包括以下內容: /** 1)接口名稱:服務端就調用的那個接口; 2)方法名:一個接口內可能有很多方法,如果不傳方法名服務端也就不知道調用哪個方法; 3)參數類型&參數值:參數類型有很多,比如有bool、int、long、double、string、map、list,甚至如struct(class);以及相應的參數值; 4)超時時間 5)requestID,標識唯一請求id */ 服務端返回的消息結構一般包括以下內容。 /** 1)返回值 2)狀態code 3)requestID */
3.4 序列化與反序列化
客戶端怎么把參數值傳給遠程的函數呢?在本地調用中,我們只需要把參數壓到棧里,然后讓函數自己去棧里讀就行。
但是在遠程過程調用時,客戶端跟服務端是不同的進程,不能通過內存來傳遞參數。
這時候就需要客戶端把參數先轉成一個字節流,傳給服務端后,再把字節流轉成自己能讀取的格式。
只有二進制數據才能在網絡中傳輸,序列化和反序列化的定義是:
- 將對象轉換成二進制流的過程叫做序列化
- 將二進制流轉換成對象的過程叫做反序列化
這個過程叫序列化和反序列化。同理,從服務端返回的值也需要序列化反序列化的過程。
目前互聯網公司廣泛使用Protobuf、Thrift、Avro等成熟的序列化解決方案來搭建RPC框架,這些都是久經考驗的解決方案。
3.5 網絡傳輸
網絡傳輸:遠程調用往往用在網絡上,客戶端和服務端是通過網絡連接的。
所有的數據都需要通過網絡傳輸,因此就需要有一個網絡傳輸層。網絡傳輸層需要把 Call ID 和序列化后的參數字節流傳給服務端,然后再把序列化后的調用結果傳回客戶端。
網絡協議 1. 盡管大部分 RPC 框架都使用 TCP 協議,但其實 UDP 也可以,而 gRPC 干脆就用了 HTTP2。 2. 在 RPC 中可選的網絡傳輸方式有多種,可以選擇 TCP 協議、UDP 協議、HTTP 協議 網絡框架 可以自己寫 Socket,或者用 Asio,ZeroMQ,Netty 之類。
網絡通信
目前有兩種常用IO通信模型:1)BIO;2)NIO。一般RPC框架需要支持這兩種IO模型。
如何實現RPC的IO通信框架呢?
1. 使用java nio方式自研,這種方式較為復雜,而且很有可能出現隱藏bug,但也見過一些互聯網公司使用這種方式;
2. 基於mina,mina在早幾年比較火熱,不過這些年版本更新緩慢;
3. 基於netty,現在很多RPC框架都直接基於netty這一IO通信框架,省力又省心,比如阿里巴巴的HSF、dubbo,Twitter的finagle等。
基於 TCP 協議的 RPC 調用 /** 由服務的調用方與服務的提供方建立 Socket 連接,並由服務的調用方通過 Socket 將需要調用的接口名稱、方法名稱和參數序列化后傳遞給服務的提供方,服務的提供方反序列化后再利用反射調用相關的方法。 但是在實例應用中則會進行一系列的封裝,如 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 協議的數據效率要低,信息傳輸所占用的時間也會更長,當然壓縮數據,能夠縮小這一差距。 */
4. 摘錄網址