1. 程式人生 > >RPC框架是什麼?原理?核心點?使用?

RPC框架是什麼?原理?核心點?使用?

RPC框架

遠端過程呼叫協議RPC(Remote Procedure Call Protocol)

首先了解什麼叫RPC,為什麼要RPC,RPC是指遠端過程呼叫。即兩臺伺服器A和B,一個應用部署在A上想要訪問位於B上應用提供的函式、方法,由於不在一個記憶體空間,不能直接呼叫,需要通過網路來表達呼叫的語意以及傳達呼叫的資料。

它是一種程式設計模式,把對伺服器的呼叫抽象成過程呼叫,通常還伴隨著框架程式碼自動生成等功能。使用RPC做網路服務開發時,通常只需要實現伺服器端的一個處理函式,其餘的客戶端呼叫,序列化反序列化,方法派發(收到什麼樣的訊息,呼叫伺服器端的什麼函式)等都由框架或者生成的程式碼來完成,較大地減輕了網路服務開發和呼叫的複雜性。

也就是說兩臺伺服器A,B,一個應用部署在A伺服器上,想要呼叫B伺服器上應用提供的函式/方法,由於不在一個記憶體空間,不能直接呼叫,需要通過網路來表達呼叫的語義和傳達呼叫的資料。比如說,一個方法可能是這樣定義的: Employee getEmployeeByName(String fullName)那麼:

  1. 首先,要解決通訊的問題,主要是通過在客戶端和伺服器之間建立TCP連線,遠端過程呼叫的所有交換的資料都在這個連線裡傳輸。連線可以是按需連線,呼叫結束後就斷掉,也可以是長連線,多個遠端過程呼叫共享同一個連線。

  2. 第二,要解決定址的問題,也就是說,A伺服器上的應用怎麼告訴底層的RPC框架,如何連線到B伺服器(如主機或IP地址)以及特定的埠,方法的名稱名稱是什麼,這樣才能完成呼叫。比如基於Web服務協議棧的RPC,就要提供一個endpoint 
    URI,或者是從UDDI服務上查詢。如果是RMI呼叫的話,還需要一個RMI Registry來註冊服務的地址。

  3. 第三,當A伺服器上的應用發起遠端過程呼叫時,方法的引數需要通過底層的網路協議如TCP傳遞到B伺服器,由於網路協議是基於二進位制的,記憶體中的引數的值要序列化成二進位制的形式,也就是序列化(Serialize)或編組(marshal),通過定址和傳輸將序列化的二進位制傳送給B伺服器。

  4. 第四,B伺服器收到請求後,需要對引數進行反序列化(序列化的逆操作),恢復為記憶體中的表達方式,然後找到對應的方法(定址的一部分)進行本地呼叫,然後得到返回值。

  5. 第五,返回值還要傳送回伺服器A上的應用,也要經過序列化的方式傳送,伺服器A接到後,再反序列化,恢復為記憶體中的表達方式,交給A伺服器上的應用

    這裡寫圖片描述 

為什麼使用RPC?

其實這是應用開發到一定的階段的強烈需求驅動的。

1. 如果我們開發簡單的單一應用,邏輯簡單、使用者不多、流量不大,那我們用不著;

2. 當我們的系統訪問量增大、業務增多時,我們會發現一臺單機執行此係統已經無法承受。此時,我們可以將業務拆分成幾個互不關聯的應用,分別部署在各自機器上,以劃清邏輯並減小壓力。此時,我們也可以不需要RPC,因為應用之間是互不關聯的。
3. 當我們的業務越來越多、應用也越來越多時,自然的,我們會發現有些功能已經不能簡單劃分開來或者劃分不出來。此時,可以將公共業務邏輯抽離出來,將之組成獨立的服務Service應用 。而原有的、新增的應用都可以與那些獨立的Service應用 互動,以此來完成完整的業務功能。所以此時,我們急需一種高效的應用程式之間的通訊手段來完成這種需求,所以你看,RPC大顯身手的時候來了!
其實3描述的場景也是服務化 、微服務 和分散式系統架構 的基礎場景。即RPC框架就是實現以上結構的有力方式。

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)服務暴露:

遠端提供者需要以某種形式提供服務呼叫相關的資訊,包括但不限於服務介面定義資料結構、或者中間態的服務定義檔案。例如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 等  

原文參考:https://blog.csdn.net/KingCat666/article/details/78577079