1. 程式人生 > >RPC入門總結(一)RPC定義和原理

RPC入門總結(一)RPC定義和原理

一、RPC

1. RPC是什麼

RPC(Remote Procedure Call Protocol)——遠端過程呼叫協議,它是一種通過網路從遠端計算機程式上請求服務,而不需要了解底層網路技術的協議。RPC協議假定某些傳輸協議的存在,如TCP或UDP,為通訊程式之間攜帶資訊資料。在OSI網路通訊模型中,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 個部分的關係如下圖所示


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


粗粒度的 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)執行緒消耗可以去掉。但不能做到像訊息一樣暫存訊息/請求,壓力會直接傳導到服務Provider2. 適用場合差異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. 編碼內容:出於效率考慮,編碼的資訊越少越好(傳輸資料少),編碼的規則越簡單越好(執行效率高)。如下是編碼需要具備的資訊:

-- 呼叫編碼 --  
1. 介面方法  
   包括介面名、方法名  
2. 方法引數  
   包括引數型別、引數值  
3. 呼叫屬性  
   包括呼叫屬性資訊,例如呼叫附件隱式引數、呼叫超時時間等  
  
-- 返回編碼 --  
1. 返回結果  
   介面方法中定義的返回值  
2. 返回碼  
   異常返回碼  
3. 返回異常資訊  
   呼叫異常資訊 
除了以上這些必須的呼叫資訊,我們可能還需要一些元資訊以方便程式編解碼以及未來可能的擴充套件。這樣我們的編碼訊息裡面就分成了兩部分,一部分是元資訊、另一部分是呼叫的必要資訊。如果設計一種 RPC 協議訊息的話,元資訊我們把它放在協議訊息頭中,而必要資訊放在協議訊息體中。下面給出一種概念上的 RPC 協議訊息設計格式:
-- 訊息頭 --  
magic      : 協議魔數,為解碼設計  
header size: 協議頭長度,為擴充套件設計  
version    : 協議版本,為相容設計  
st         : 訊息體序列化型別  
hb         : 心跳訊息標記,為長連線傳輸層心跳設計  
ow         : 單向訊息標記,  
rp         : 響應訊息標記,不置位預設是請求訊息  
status code: 響應訊息狀態碼  
reserved   : 為位元組對齊保留  
message id : 訊息 id  
body size  : 訊息體長度  
  
-- 訊息體 --  
採用序列化編碼,常見有以下格式  
xml   : 如 webservie soap  
json  : 如 JSON-RPC  
binary: 如 thrift; hession; kryo 等  

以上就是RPC的核心技術點,包含內容繁多,下面本博就在學習的基礎上以三種RPC框架為例介紹RPC的各項關鍵技術。