簡易RPC框架:需求與設計

1 需求分析

RPC 全稱 Remote Procedure Call ,簡單地來講,它能讓使用者像調用本地方法同樣,調用遠程的接口,而不須要關注底層的具體細節。編程

例如車輛違章代辦功能,若是車輛由於某種緣由違章,只須要經過這個違章代辦功能(它也許是個APP),咱們就能動動手指,而省去了一些跑腿的工做。安全

不像微服務背景下你們所說的 RPC 框架,如 Dubbo 之類。這個 RPC 框架不提供過多的關於服務註冊、服務發現、服務管理等功能。它針對的是這樣的一些場景:在內部網絡,或者局域網內,兩個屬於同個業務的系統之間須要通訊,而咱們又以爲去設計多一種二進制網絡協議過於繁瑣而且沒有必要,這時候若是給客戶端開發者一些明確的接口,讓他知道實現什麼功能該調用什麼接口,那麼省去的工做量以及開發效率上的提高不言而喻。微信

這個 RPC 系統基於 Java 語言實現,需求以下:網絡

  • RPC 服務端能夠經過一條長鏈接發佈多個接口(Interface),客戶端按需生成對應接口的代理。
  • RPC 客戶端也能夠發佈接口,以便在必要的時候,服務端能夠主動調用客戶端的接口實現
  • 客戶端與服務端之間保持長鏈接而且維持心跳
  • 服務端針對不一樣的接口實現,能夠指定不一樣的線程池去處理
  • 序列化協議支持擴展
  • 通訊協議與具體編程語言無關
  • 支持併發調用,一個RPC客戶端實例要求是線程安全的

2. 通訊協議設計

高效的通訊協議通常是二進制格式的,比較常見的還有文本協議好比說HTTP,爲了追求效率,這個 RPC 框架就採用二進制格式。併發

協議的基本要素

魔數

要了解到,報文是在網絡上傳輸的,安全性比較低,所以有必要採起一些措施使得並非任何人均可以隨隨便便往咱們的端口上發東西,所以咱們對報文要有一個初步的識別功能,這時候「魔數(magic number)」就派上用場了。魔數並不受任何規範約束,沒有人能夠要求你的魔數應該遵循什麼規範,實際上魔數只是咱們通訊雙方都約定的一個「暗號」,不知道這個暗號的人就沒法參與進通訊中。例如 Java 源文件編譯後的 class 文件開頭就有一個魔數:0xCAFEBABE,隨隨便便打開一個class文件用十六進制編輯器查看,就能看到。負載均衡

class文件

Java 虛擬機加載 class 的時候會先驗證魔數。若是不是 CAFEBABE 就認爲是不合法的 class 文件,並拒絕加載。框架

不過魔數起到的安全防範做用是很是有限的,「有心人」能夠經過抓取網絡包就識別出魔數了。所以魔數這個東西實際上是「防君子不防小人」。編程語言

協議版本

一個協議可能也會有多個版本,例如說 HTTP1.0 和 HTTP1.1,不一樣版本的協議元素可能發生了改變,解析方式也會發生改變,所以協議設計這一塊,須要預留出地方聲明協議的版本,通訊雙方在解析協議或者拼裝協議的時候纔有跡可循。編輯器

報文類型

對於RPC框架來講,報文可能有多種類型:心跳類型報文、認證類型報文、請求類型報文、響應類型報文等。ide

上下文 ID

RPC 調用實際上是一個「請求-響應」的過程,而且跨物理機器,所以每次請求和響應,都必須帶上上下文 ID,通訊雙方纔能把請求和響應對應起來。

狀態

狀態用來標識一次調用時正常結束仍是異常結束,一般由被調用方置狀態。

請求數據

即發送到服務端的調用請求,一般是序列化後的二進制流,長度不定。

長度編碼字段

收報文的一方怎麼知道發報文的那一方發了多少字節呢?所以發送方必須在協議裏告訴接收方須要接受多少字節纔算一個完整的報文。

保留字段

協議一旦被設計,並不是一成不變的,往後可能有變更的可能,所以還須要考慮保留一些字節空間做爲保留字段,以備往後協議的擴展。

協議設計

結合以上的一些設計原則,具體協議設計以下:

------------------------------------------------------------------------
 | magic (2bytes) | version (1byte) |  type (1byte)  | reserved (7bits) | 
 ------------------------------------------------------------------------
 | status (1byte) |    id (8bytes)    |        body length (4bytes)     |
 ------------------------------------------------------------------------
 |                                                                      |
 |                   body ($body_length bytes)                          |
 |                                                                      |
 ------------------------------------------------------------------------

3. 鏈路可靠性

客戶端與服務端之間的鏈接採用 TCP 長鏈接,一個客戶端與服務端之間保持至少一條長鏈接。接口調用請求的發送,在多條鏈接之間進行負載均衡。

每條鏈接在空閒的時候,由客戶端主動向服務端發送心跳報文,而且客戶端在發現鏈接失效或斷開的時候,自動進行重連。

每一個客戶端向服務端創建鏈接後,在正式發起接口調用請求以前,都須要進行check in 操做, check in 操做主要是將客戶端的身份標識(identifier)和客戶端的心跳間隔告訴服務端。利用 netty 的 handler 責任鏈機制和自帶的 IdleStateHandler,自動檢測出鏈接是否空閒,並在空閒時觸發心跳報文的發送。而服務端在客戶端 checkin 後,根據客戶端的心跳頻率,在本身的 handler pipeline 上動態加入一個 IdleStateHandler,來檢測出客戶端是否已經失聯,若是是,則主動關閉鏈接。

同時,客戶端本地將會起一個定時執行任務的線程,按期檢查鏈接是否失效,若是失效,則關閉舊鏈接,並進行鏈接的重建。

掃一掃關注個人微信公衆號

相關文章
相關標籤/搜索