天天看點

分布式通信架構-RMI(分布式筆記)

RPC(Remote procedure call protocol)遠端過程調用協定

1、什麼是RPC

RPC協定(一種通過網絡從遠端計算機程式上請求服務,而不需要了解底層網絡技術的協定),其實是一個規範。RPC主要解決的兩個問題:

  1. 解決分布式系統中,服務之間的調用問題。
  2. 遠端調用時,要能夠像本地調用一樣友善,讓調用者感覺不到遠端調用的邏輯。

2、為什麼要用RPC?

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

  1. 如果我們開發簡單的單一應用,邏輯簡單、使用者不多、流量不大,那我們用不着;
  2. 當我們的系統通路量增大、業務增多時,我們會發現一台單機運作此系統已經無法承受。此時,我們可以将業務拆分成幾個互不關聯的應用,分别部署在各自機器上,以劃清邏輯并減小壓力。此時,我們也可以不需要RPC,因為應用之間是互不關聯的。
  3. 當我們的業務越來越多、應用也越來越多時,自然的,我們會發現有些功能已經不能簡單劃分開來或者劃分不出來。此時,可以将公共業務邏輯抽離出來,将之組成獨立的服務Service應用 。而原有的、新增的應用都可以與那些獨立的Service應用 互動,以此來完成完整的業務功能。是以此時,我們急需一種高效的應用程式之間的通訊手段來完成這種需求,是以你看,RPC大顯身手的時候來了!

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

3、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.0 支援。
  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 等

3、一個RPC架構包含的要素

分布式通信架構-RMI(分布式筆記)

4、RPC vs Restful

其實這兩者并不是一個次元的概念,總得來說RPC涉及的次元更廣。如果硬要比較,那麼可以從RPC風格的url和Restful風格的url上進行比較。比如你提供一個查詢訂單的接口,用RPC風格,你可能會這樣寫:/queryOrder?orderId=123

用Restful風格呢?Get /order?orderId=123

RPC是面向過程,Restful是面向資源,并且使用了Http動詞。從這個次元上看,Restful風格的url在表述的精簡性、可讀性上都要更好。

5、RPC vs RMI

嚴格來說這兩者也不是一個次元的。RMI是Java提供的一種通路遠端對象的協定,是已經實作好了的,可以直接用了。而RPC呢?人家隻是一種程式設計模型,并沒有規定你具體要怎樣實作,你甚至都可以在你的RPC架構裡面使用RMI來實作資料的傳輸,比如Dubbo:Dubbo - rmi協定。

RMI的概述

1、什麼是RMI

RMI(remote method invocation) , 可以認為是RPC的java版實作,隻能支援Java語言,RMI使用的是JRMP(Java Remote Messageing Protocol)Java遠端資訊交換協定, JRMP是專門為java定制的分布式通信解決方案

2、如何實作一個RMI程式

  1. 建立遠端接口, 并且繼承java.rmi.Remote接口
  2. 實作遠端接口,并且繼承:UnicastRemoteObject
  3. 建立伺服器程式: createRegistry方法注冊遠端對象
  4. 建立用戶端程式

自己實作一個簡單的RMI

1、RMI工作原理

分布式通信架構-RMI(分布式筆記)

方法調用從客戶對象經占位程式(Stub)、遠端引用層(Remote Reference Layer)和傳輸層(Transport Layer)向下,傳遞給主機,然後再次經傳 輸層,向上穿過遠端調用層和骨幹網(Skeleton),到達伺服器對象。 占位程式扮演着遠端伺服器對象的代理角色,使該對象可被客戶激活。 遠端引用層處理語義、管理單一或多重對象的通信,決定調用是應發往一個伺服器還是多個。傳輸層管理實際的連接配接,并且追蹤可以接受方法調用的遠端對象。伺服器端的骨幹網完成對伺服器對象實際的方法調用,并擷取傳回值。傳回值向下經遠端引用層、伺服器端的傳輸層傳遞回用戶端,再向上經傳輸層和遠端調用層傳回。最後,占位程式獲得傳回值。 要完成以上步驟需要有以下幾個步驟:

  1. 生成一個遠端接口
  2. 實作遠端對象(伺服器端程式)
  3. 生成占位程式和骨幹網(伺服器端程式)
  4. 編寫伺服器程式
  5. 編寫客戶程式
  6. 注冊遠端對象
  7. 啟動遠端對象

2、RMI 應用各個類的互動時序圖

分布式通信架構-RMI(分布式筆記)

3、實作思路

  1. 編寫伺服器程式,暴露一個監聽, 可以使用ServerSocket
  2. 編寫用戶端程式,通過ip和端口連接配接到指定的伺服器,并将資料序列化 傳遞給伺服器
  3. 伺服器端收到資料,先反序列化,再進行業務邏輯處理,最後把處理結果序列化并傳回

繼續閱讀