Rpc基礎 原理 框架
一. RPC的原理
1.RPC是什麼
RPC(Remote Procedure Call Protocol)——遠端過程呼叫協議,它是一種通過網路從遠端計算機程式上請求服務,而不需要了解底層網路技術的協議。RPC協議假定某些傳輸協議的存在,如TCP或UDP,為通訊程式之間攜帶資訊資料。在OSI網路通訊模型中,RPC跨越了傳輸層和應用層。RPC使得開發包括網路分散式多程式在內的應用程式更加容易。
2. 為什麼要用RPC?
其實這是應用開發到一定的階段的強烈需求驅動的。
1. 如果我們開發簡單的單一應用,邏輯簡單、使用者不多、流量不大,那我們用不著;
2. 當我們的系統訪問量增大、業務增多時,我們會發現一臺單機執行此係統已經無法承受。此時,我們可以將業務拆分成幾個互不關聯的應用,分別部署在各自機器上,以劃清邏輯並減小壓力。此時,我們也可以不需要RPC,因為應用之間是互不關聯的。
3. 當我們的業務越來越多、應用也越來越多時,自然的,我們會發現有些功能已經不能簡單劃分開來或者劃分不出來。此時,可以將公共業務邏輯抽離出來,將之組成獨立的服務Service應用 。而原有的、新增的應用都可以與那些獨立的Service應用 互動,以此來完成完整的業務功能。所以此時,我們急需一種高效的應用程式之間的通訊手段來完成這種需求,所以你看,RPC大顯身手的時候來了!
其實3描述的場景也是服務化 、微服務
二. RPC的整體框架
從整體微粒度來看RPC在程式中所起的功能
三. RPC的核心特點
(1)服務暴露:
遠端提供者需要以某種形式提供服務呼叫相關的資訊,包括但不限於服務介面定義、資料結構、或者中間態的服務定義檔案。
目前,大部分跨語言平臺 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 等