帶你手把手實操一個RPC框架
這篇文章我們來聊一聊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等,針對于上面功能的實現方式也有所不同,以下是注冊中心的對比:
Zookeeper | Nacos | Consul | Eurake | |
一致性協議 | CP | CP + AP | CP | AP |
雪崩保護 | 無 | 有 | 無 | 有 |
多數據中心 | 不支持 | 支持 | 支持 | 支持 |
自動注銷實例 | 支持 | 支持 | 支持 | 支持 |
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有更清晰的了解,讓自己的基礎技能有一個更高的提升。