帶你手把手實操一個RPC框架

得物技術發表於2022-12-16

前言:

這篇文章我們來聊一聊RPC框架,為什麼要聊RPC呢 ?

首先從個人成長角度,如果一個新時程式碼農能清楚的瞭解RPC框架所具備的要素,掌握RPC框架中涉及的服務註冊發現、負載均衡、序列化協議、RPC通訊協議、Socket通訊、非同步呼叫、熔斷降級等技術,可以全方位的提升基本素質。

其次,目前市面上也有非常多優秀的框架,GitHub上也有相關原始碼,但好記性不如爛筆頭,只有自己真正瞭解並且動手去嘗試寫一個RPC框架,才是我們去掌握這門技術的最優路徑。

一、介紹

研究一個概念或者框架,帶著三個W去考慮,可能會對他有更加深刻的瞭解:

1)What,什麼是RPC框架,RPC是 Remote Procedure Call 的簡稱,遠端過程呼叫,那麼什麼叫遠端過程呼叫呢,你可以理解為我們呼叫外部(遠端)服務就像呼叫自己本地方法一樣。

2)Where,RPC框架用在什麼地方,在分散式系統和微服務盛行的今天,各業務系統會被獨立拆分出來成為一個個獨立的web應用,應用之間的互動和資料傳輸就成了必不可少的一環,RPC就是為了實現獨立服務之間遠端互動的框架。

3)Why,為什麼需要一個RPC框架,服務之間的呼叫需要各種場景和因素的考慮,內部原理非常複雜和繁瑣,同時在叢集情況下,服務的負載均衡,熔斷,限流等都是需要去考慮的,這時候就需要一個集服務註冊發現、負載均衡、序列化協議、RPC通訊協議、Socket通訊、非同步呼叫、熔斷降級等技術為一體的技術去完成這些公共功能,RPC框架就是在這種情況下應運而生。

目前比較主流的RPC框架包括谷歌開源的GRPC、阿里巴巴的Dubbo、Netflix 的SpringCloud等。

二、RPC框架基本組成

RPC框架需要的最基本的三個要素:

  • ServiceProvider: 服務提供方,提供相關服務介面。
  • ServiceConsumer: 服務消費方,消費服務提供方的介面。
  • Registry: 註冊中心,用於進行服務的註冊、發現、治理、高可用。

基於三個最基本要素,還會延伸出包括負載均衡器、熔斷降級器、通訊協議元件、序列化協議等等元件。

一個最簡單的RPC呼叫模型圖如下所示:

下面做一些名詞的介紹和解釋:

2.1 註冊中心

註冊中心是RPC框架中的管理者和協調者角色,雖然在遠端過程呼叫中服務消費者會不經過註冊中心,會直接向服務提供者傳送請求,但是隨著我們的服務方越來越多,每個服務的例項也不斷變化的,且每個服務的地址,埠等資訊是需要通知到消費方的,所以我們需要一個類似“管家”的角色,來負責管理服務註冊和發現的工作,這個“管家”我們稱之為註冊中心。

一個合格的註冊中心需要具備包括快取和持久化服務提供方資料,動態更新服務提供者資訊,動態監聽服務提供方節點變化,推送節點變化到消費方,查詢服務提供方資料等功能。

目前市面上比較流行的註冊中心有:Zookper、 Nacos、Consul、Eurake等,針對於上面功能的實現方式也有所不同,以下是註冊中心的對比:

ZookeeperNacosConsulEurake
一致性協議CPCP + APCPAP
雪崩保護
多資料中心不支援支援支援支援
自動登出例項支援支援支援支援

2.2 服務提供方(RPC服務端)

其需要對外提供服務介面,一個服務方需要包括啟動連線註冊中心,註冊相關資訊到註冊中心,提供服務下線和更新機制,維護服務名和服務的對映,序列化和反序列化,啟動通訊等

目前服務提供方有兩種服務提供維度,基於介面的服務提供和基於服務的服務提供,Dubbo3.0 之前是基於介面維度做的服務註冊,Dubbo3.0之後漸漸向服務維度的服務註冊發現靠攏,SpringCloud是基於服務來進行註冊發現的,在雲原生和容器化越來越火的今天,基於服務可以更好的適配容器化和雲原生。

2.3 服務消費方(RPC消費端)

服務消費方需要具備可以從註冊中心拉取服務列表,快取服務列表,動態監聽和更新服務列表的功能,還需要具備針對於服務的負載均衡策略,序列化和反序列化,根據約定的通訊協議進行呼叫等。

目前Dubbo是基於代理和Spring的BeanDefination來實現的,在消費啟動的時候會去掃描基於自定義註解或配置的資訊,然後生成一個相應的代理物件,註冊到Spring容器中,在呼叫的時候直接透過代理類進行相關一系列呼叫。SpringCloud是基於HTTP協議和增強版的RestTemplate來實現的,內部實現了Ribbon的負載均衡,消費方可以透過Feign或者RestTemplate實現遠端呼叫。

2.4 通訊框架

通訊框架是服務之間進行IO互動和傳輸的保證,消費端需要透過通訊框架和提供方進行互動,獲取資料,目前市面上主流的基於Java的NIO通訊框架就是Netty。

2.5 通訊協議

通訊協議是消費端和服務端約定好一種互動協議,當消費端拿到服務端提供的IO流之後,需要根據通訊協議獲取具體的資料內容,目前TCP通訊協議比較通用的是HTTP、FTP、SMTP協議等,SpringCloud是基於HTTP的通訊協議實現的,Dubbo內部自己整合了一套通訊協議。

業界的主流協議的解決方案可以歸納如下:

  • 訊息定長,例如每個報文的大小為固定長度100位元組,如果不夠用空格補足。
  • 在包尾特殊結束符進行分割。
  • 將訊息分為訊息頭和訊息體,訊息頭中包含表示訊息總長度(或者訊息體長度)的欄位。

透過對比,我們發現第三點是最為靈活和可擴充的,一般推薦都會使用第三種。

2.6 序列化

2.6.1 概念介紹

序列化(serialization)就是將物件序列化為二進位制形式(位元組陣列),一般也將序列化稱為編碼(Encode),主要用於網路傳輸、資料持久化等。

反序列化(deserialization)則是將從網路、磁碟等讀取的位元組陣列還原成原始物件,以便後續業務的進行,一般也將反序列化稱為解碼(Decode),用於網路傳輸物件的解碼,以便完成遠端呼叫。

2.6.2 序列化協議

  • XML & SOAP

XML是一種常用的序列化和反序列化協議,具有跨機器,跨語言等優點。XML歷史悠久,其1.0版本早在1998年就形成標準,並被廣泛使用至今,目前金融和銀行行業使用較多。

  • JSON

JSON 全稱 (Javascript Object Notation) 起源於弱型別語言Javascript, 它的產生來自於一種稱之為”Associative array”的概念,其本質是就是採用”Attribute-value”的方式來描述物件。實際上在Javascript和PHP等弱型別語言中,類的描述方式就是Associative array。JSON的具有資料簡單,可接受程度高,結構簡潔,序列化包體小等特點,目前大部分的公司都是使用這種序列化協議來實現的。

  • Protobuf

由谷歌開發的一款高效能序列化框架,是一個純粹的展示層協議,可以和各種傳輸層協議一起使用,目前支援Java、C++、Python 等多種語言,他具有更小的資料量,更快的解析速度,簡單的呼叫等特點,目前得物使用的Dubbo2.7.7版本 預設使用這種序列化協議。

2.7 負載均衡

負載均衡是保證服務提供方在多例項的情況下保證負載的均衡的一種策略,目前大體有如下幾種負載均衡策略:

1)輪訓,採用計數器的方式,根據計數器的值和例項數量進行取餘。

2)隨機,採用隨機請求的方式,隨機一個Random的數值,根據random進行取餘。

3)加權輪訓,採用權重的方式,給每一個例項配置不同的權重比例,透過比例選擇合適的例項。

4) 一致性Hash,採用Hash環的方式,大體的實現思路是透過定址的方式找到就近的一個節點,具體可以自行網上搜尋一下相關文件理解。

三、實現

既然是當作練手實現一個RPC框架,所以會盡量借鑑當前主流的框架,技術選型方面:

註冊中心:選擇Zookeeper來實現。

服務提供方: 選擇基於服務維度來實現服務發現,目前主流框架都是基於這個來做的。

服務消費方: 選擇Dubbo類似的基於代理Spring的BeanDefination來實現。

通訊框架: Netty。

通訊協議: 採用上述的第三種方式。

序列化協議: 支援JSON 和 Protobuf。

負載均衡: 實現輪訓和隨機。

當然,上述的元件都是使用SpringBoot的SPI方式來進行選擇的,後續如果需要進行擴充和按不同配置載入,可以透過配置的方式來實現外掛的可插拔。

廢話不多說,先貼上專案整體結構:

整體程式碼結構如上,下面針對模組一一講解:

3.1 註冊中心

程式碼實現如下:

因為我們是以可擴充和介面方式實現的,所以我們定義了一些介面,透過不同的實現來進行區分,後期可以透過不同的配置來選擇合適的註冊中心。

3.2 服務提供方

服務提供方透過使用Spring的事件來進行監聽,同時根據宣告式的註解來進行解析和註冊,同時透過組裝後設資料,將自己的服務註冊到註冊中心,完成服務提供方的服務註冊,同時啟動Netty的客戶端,來進行客戶端的監聽,從而進行服務呼叫,具體流程圖如下:

部分實現程式碼如下:

自動裝備邏輯:

使用和服務註冊邏輯:

3.3 服務消費方

服務消費方的邏輯稍微複雜一些,需要透過自動裝配來建立新的BeanDefination, 然後從所有的Bean中找到相關的帶有RPC註解的引數,重寫BeanDefination,重寫的Bean需要構建新的後設資料和存入客戶端快取,然後透過動態代理的方式,建立一個代理物件,物件使用負載均衡在服務發現的時候選擇一個地址,透過Netty的方式進行通訊和資料互動,最後返回相關資料物件,具體的流程如下圖:

部分實現程式碼如下:

啟動開始類程式碼:

代理類實現程式碼:

負載均衡實現程式碼:(SPI可擴充模式)

目前採用輪訓和隨機的方式實現的,後期可根據介面進行擴充。

3.4 通訊框架

我們使用Netty4實現了通訊框架,採用了NettyClient和NettyServer來實現:

3.5 通訊協議

我們目前使用了自定義的通訊協議來實現:

第一個位元組是魔法數,比如我定義為0X35。

第二個位元組代表協議版本號,以便對協議進行擴充套件,使用不同的協議解析器。

第三個位元組是請求型別,如0代表請求1代表響應。

第四個位元組表示訊息長度,即此四個位元組後面此長度的內容是訊息content。

3.6 序列化協議

目前支援JSON和ProtoBuf,後期我們可以透過SPI進行擴充和可配置。

整體核心呼叫流程:

四、總結

本文從整體名詞介紹、內部元件元件介紹等兩個方面闡述了RPC的框架模型,從技術選型、具體程式碼等實現了一個RPC框架並應用到專案中。目前RPC框架整體搭建完成,可以正常透過註解接入業務方使用,但還有很多細節需要更仔細地去考慮和思索,比如系統的可擴充性和框架的整體效能等。希望透過閱讀本篇文章,可以讓讀者對RPC有更清晰的瞭解,讓自己的基礎技能有一個更高的提升。

*文/Wade

關注得物技術,每週一三五晚18:30更新技術乾貨

要是覺得文章對你有幫助的話,歡迎評論轉發點贊~

相關文章