RPC總結——RPC定義和原理,一篇就夠了

一、RPC

1. RPC是什麼

RPC(Remote Procedure Call Protocol)——遠程過程調用協議,它是一種通過網絡從遠程計算機程序上請求服務,而不需要了解底層網絡技術的協議。RPC協議假定某些傳輸協議的存在,如TCP或UDP,為通信程序之間攜帶信息數據。在OSI網絡通信模型中,RPC跨越了傳輸層應用層。RPC使得開發包括網絡分佈式多程序在內的應用程序更加容易。

RPC採用客戶機/服務器模式。請求程序就是一 個客戶機,而服務提供程序就是一個服務器。首先,客戶機調用進程發送一個有進程參數的調用信息到服務進程,然後等待應答信息。在服務器端,進程保持睡眠狀 態直到調用信息到達為止。當一個調用信息到達,服務器獲得進程參數,計算結果,發送答覆信息,然後等待下一個調用信息,最後,客戶端調用進程接收答覆信 息,獲得進程結果,然後調用執行繼續進行。

RPC總結——RPC定義和原理,一篇就夠了

2. 為什麼要用RPC?

其實這是應用開發到一定的階段的強烈需求驅動的。1. 如果我們開發簡單的單一應用,邏輯簡單、用戶不多、流量不大,那我們用不著;

2. 當我們的系統訪問量增大、業務增多時,我們會發現一臺單機運行此係統已經無法承受。此時,我們可以將業務拆分成幾個互不關聯的應用,分別部署在各自機器上,以劃清邏輯並減小壓力。此時,我們也可以不需要RPC,因為應用之間是互不關聯的。

3. 當我們的業務越來越多、應用也越來越多時,自然的,我們會發現有些功能已經不能簡單劃分開來或者劃分不出來。此時,可以將公共業務邏輯抽離出來,將之組成獨立的服務Service應用 。而原有的、新增的應用都可以與那些獨立的Service應用 交互,以此來完成完整的業務功能。所以此時,我們急需一種高效的應用程序之間的通訊手段來完成這種需求,所以你看,RPC大顯身手的時候來了!

其實3描述的場景也是服務化 、微服務分佈式系統架構 的基礎場景。即RPC框架就是實現以上結構的有力方式。

二、RPC的原理和框架

Nelson 的論文中指出實現 RPC 的程序包括 5 個部分:

1. User

2. User-stub

3. RPCRuntime

4. Server-stub

5. Server

這 5 個部分的關係如下圖所示

RPC總結——RPC定義和原理,一篇就夠了

這裡 user 就是 client 端,當 user 想發起一個遠程調用時,它實際是通過本地調用user-stub。user-stub 負責將調用的接口、方法和參數通過約定的協議規範進行編碼並通過本地的 RPCRuntime 實例傳輸到遠端的實例。遠端 RPCRuntime 實例收到請求後交給 server-stub 進行解碼後發起本地端調用,調用結果再返回給 user 端。

RPC總結——RPC定義和原理,一篇就夠了

粗粒度的 RPC 實現概念結構,這裡我們進一步細化它應該由哪些組件構成,如下圖所示。

RPC總結——RPC定義和原理,一篇就夠了

RPC 服務方通過 RpcServer 去導出(export)遠程接口方法,而客戶方通過 RpcClient 去引入(import)遠程接口方法。客戶方像調用本地方法一樣去調用遠程接口方法,RPC 框架提供接口的代理實現,實際的調用將委託給代理RpcProxy 。代理封裝調用信息並將調用轉交給RpcInvoker 去實際執行。在客戶端的RpcInvoker 通過連接器RpcConnector 去維持與服務端的通道RpcChannel,並使用RpcProtocol 執行協議編碼(encode)並將編碼後的請求消息通過通道發送給服務方。

RPC 服務端接收器 RpcAcceptor 接收客戶端的調用請求,同樣使用RpcProtocol 執行協議解碼(decode)。解碼後的調用信息傳遞給RpcProcessor 去控制處理調用過程,最後再委託調用給RpcInvoker 去實際執行並返回調用結果。如下是各個部分的詳細職責:

1. RpcServer

負責導出(export)遠程接口

2. RpcClient

負責導入(import)遠程接口的代理實現

3. RpcProxy

遠程接口的代理實現

4. RpcInvoker

客戶方實現:負責編碼調用信息和發送調用請求到服務方並等待調用結果返回

服務方實現:負責調用服務端接口的具體實現並返回調用結果

5. RpcProtocol

負責協議編/解碼

6. RpcConnector

負責維持客戶方和服務方的連接通道和發送數據到服務方

7. RpcAcceptor

負責接收客戶方請求並返回請求結果

8. RpcProcessor

負責在服務方控制調用過程,包括管理調用線程池、超時時間等

9. RpcChannel

數據傳輸通道

三、Java中常用的RPC框架

目前常用的RPC框架如下:

1. Thrift:thrift 是一個軟件框架,用來進行可擴展且跨語言的服務的開發。它結合了功能強大的軟件堆棧和代碼生成引擎,以構建在 C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml 這些編程語言間無縫結合的、高效的服務。

2. Dubbo:Dubbo是一個分佈式服務框架,以及SOA治理方案。其功能主要包括:高性能NIO通訊及多協議集成,服務動態尋址與路由,軟負載均衡與容錯,依賴分析與降級等。 Dubbo是阿里巴巴內部的SOA服務化治理方案的核心框架,Dubbo自2011年開源後,已被許多非阿里系公司使用。

3. Spring Cloud:Spring Cloud由眾多子項目組成,如Spring Cloud Config、Spring Cloud Netflix、Spring Cloud Consul 等,提供了搭建分佈式系統及微服務常用的工具,如配置管理、服務發現、斷路器、智能路由、微代理、控制總線、一次性token、全局鎖、選主、分佈式會話 和集群狀態等,滿足了構建微服務所需的所有解決方案。Spring Cloud基於Spring Boot, 使得開發部署極其簡單。

四、RPC和消息隊列的差異

1. 功能差異

在架構上,RPC和Message的差異點是,Message有一箇中間結點Message Queue,可以把消息存儲。

消息的特點

1. Message Queue把請求的壓力保存一下,逐漸釋放出來,讓處理者按照自己的節奏來處理。

2. Message Queue引入一下新的結點,系統的可靠性會受Message Queue結點的影響。

3. Message Queue是異步單向的消息。發送消息設計成是不需要等待消息處理的完成。

所以對於有同步返回需求,用Message Queue則變得麻煩了。

RPC的特點

同步調用,對於要等待返回結果/處理結果的場景,RPC是可以非常自然直覺的使用方式(RPC也可以是異步調用)。

由於等待結果,Consumer(Client)會有線程消耗。如果以異步RPC的方式使用,Consumer(Client)線程消耗可以去掉。但不能做到像消息一樣暫存消息/請求,壓力會直接傳導到服務Provider。

2. 適用場合差異

1. 希望同步得到結果的場合,RPC合適。

2. 希望使用簡單,則RPC;RPC操作基於接口,使用簡單,使用方式模擬本地調用。異步的方式編程比較複雜。

3. 不希望發送端(RPC Consumer、Message Sender)受限於處理端(RPC Provider、Message Receiver)的速度時,使用Message Queue。

隨著業務增長,有的處理端處理量會成為瓶頸,會進行同步調用到異步消息的改造。這樣的改造實際上有調整業務的使用方式。比如原來一個操作頁面提交後就下一個頁面會看到處理結果;改造後異步消息後,下一個頁面就會變成“操作已提交,完成後會得到通知”。

3. 不適用場合說明

1. RPC同步調用使用Message Queue來傳輸調用信息。 上面分析可以知道,這樣的做法,發送端是在等待,同時佔用一箇中間點的資源。變得複雜了,但沒有對等的收益。

2. 對於返回值是void的調用,可以這樣做,因為實際上這個調用業務上往往不需要同步得到處理結果的,只要保證會處理即可。(RPC的方式可以保證調用返回即處理完成,使用消息方式後這一點不能保證了。)

3. 返回值是void的調用,使用消息,效果上是把消息的使用方式Wrap成了服務調用(服務調用使用方式成簡單,基於業務接口)。

五、RPC框架的核心技術點

RPC框架實現的幾個核心技術點:

(1)服務暴露:

遠程提供者需要以某種形式提供服務調用相關的信息,包括但不限於服務接口定義

數據結構、或者中間態的服務定義文件。例如Facebook的Thrift的IDL文件,Web service的WSDL文件;服務的調用者需要通過一定的途徑獲取遠程服務調用相關的信息。

目前,大部分跨語言平臺 RPC 框架採用根據 IDL 定義通過 code generator 去生成 stub 代碼,這種方式下實際導入的過程就是通過代碼生成器在編譯期完成的。代碼生成的方式對跨語言平臺 RPC 框架而言是必然的選擇,而對於同一語言平臺的 RPC 則可以通過共享接口定義來實現。這裡的導入方式本質也是一種代碼生成技術,只不過是在運行時生成,比靜態編譯期的代碼生成看起來更簡潔些。

java 中還有一種比較特殊的調用就是多態,也就是一個接口可能有多個實現,那麼遠程調用時到底調用哪個?這個本地調用的語義是通過 jvm 提供的引用多態性隱式實現的,那麼對於 RPC 來說跨進程的調用就沒法隱式實現了。如果前面DemoService 接口有 2 個實現,那麼在導出接口時就需要

特殊標記不同的實現需要,那麼遠程調用時也需要傳遞該標記才能調用到正確的實現類,這樣就解決了多態調用的語義問題。

(2)遠程代理對象:

服務調用者用的服務實際是遠程服務的本地代理。說白了就是通過動態代理來實現。

java 裡至少提供了兩種技術來提供動態代碼生成,一種是 jdk 動態代理,另外一種是字節碼生成。動態代理相比字節碼生成使用起來更方便,但動態代理方式在性能上是要遜色於直接的字節碼生成的,而字節碼生成在代碼可讀性上要差很多。兩者權衡起來,個人認為犧牲一些性能來獲得代碼可讀性和可維護性顯得更重要。

(3)通信:

RPC框架與具體的協議無關。RPC 可基於 HTTP 或 TCP 協議,Web Service 就是基於 HTTP 協議的 RPC,它具有良好的跨平臺性,但其性能卻不如基於 TCP 協議的 RPC。

1. TCP/HTTP:眾所周知,TCP 是傳輸層協議,HTTP 是應用層協議,而傳輸層較應用層更加底層,在數據傳輸方面,越底層越快,因此,在一般情況下,TCP 一定比 HTTP 快。

2. 消息ID:RPC 的應用場景實質是一種可靠的請求應答消息流,和 HTTP 類似。因此選擇長連接方式的 TCP 協議會更高效,與 HTTP 不同的是在協議層面我們定義了每個消息的唯一 id,因此可以更容易的複用連接。

3. IO方式:為了支持高併發,傳統的阻塞式 IO 顯然不太合適,因此我們需要異步的 IO,即 NIO。Java 提供了 NIO 的解決方案,Java 7 也提供了更優秀的 NIO.2 支持。

4. 多連接:既然使用長連接,那麼第一個問題是到底 client 和 server 之間需要多少根連接?實際上單連接和多連接在使用上沒有區別,對於數據傳輸量較小的應用類型,單連接

基本足夠。單連接和多連接最大的區別在於,每根連接都有自己私有的發送和接收緩衝區,因此大數據量傳輸時分散在不同的連接緩衝區會得到更好的吞吐效率。所以,如果你的數據傳輸量不足以讓單連接的緩衝區一直處於飽和狀態的話,那麼使用多連接並不會產生任何明顯的提升,反而會增加連接管理的開銷。

5. 心跳: 連接是由 client 端發起建立並維持。如果 client 和 server 之間是直連的,那麼連接一般不會中斷(當然物理鏈路故障除外)。如果 client 和 server 連接經過一些負載中轉設備,有可能連接一段時間不活躍時會被這些中間設備中斷。為了保持連接有必要定時為每個連接發送心跳數據以維持連接不中斷。心跳消息 是 RPC 框架庫使用的內部消息,在前文協議頭結構中也有一個專門的心跳位,就是用來標記心跳消息的,它對業務應用透明。

(4)序列化:

兩方面會直接影響 RPC 的性能,一是傳輸方式,二是序列化。

1. 序列化方式:畢竟是遠程通信,需要將對象轉化成二進制流進行傳輸。不同的RPC框架應用的場景不同,在序列化上也會採取不同的技術。 就序列化而言,Java 提供了默認的序列化方式,但在高併發的情況下,這種方式將會帶來一些性能上的瓶頸,於是市面上出現了一系列優秀的序列化框架,比如:Protobuf、Kryo、Hessian、Jackson 等,它們可以取代 Java 默認的序列化,從而提供更高效的性能。

2. 編碼內容:出於效率考慮,編碼的信息越少越好(傳輸數據少),編碼的規則越簡單越好(執行效率高)


分享到:


相關文章: