RPC設計概要

語言: CN / TW / HK

前言

RPC全程遠端方法呼叫,已經在各大小公司被廣泛使用,種類也是很多比如:Dubbo,Spring cloud那一套,GRPC,Thrift,可能還有很多公司自研的等等;每個公司都可能根據自己的業務需求,場景選擇自己合適的RPC框架;但大體的考察維度無非就這麼幾個:效能,可擴充套件性,跨平臺,功能性,可監控,使用性;所以我們如果要設計一個RPC框架,可以從這幾個角度去考慮。

效能

作為微服務中的核心元件,在一個系統中RPC的呼叫量往往是很高的,所以效能是一個很重要的考慮點;既然是遠端呼叫,必然牽扯到網路連線,而I/O模型的選擇直接影響到效能,網路的長連線短連線,序列化方式也都影響效能;

1.I/O模型

常見的Unix5種I/O模型分別是:阻塞I/O,非阻塞I/O,I/O複用(select,poll,epoll等支援I/O多路複用),訊號驅動I/O,非同步I/O;從早期的阻塞I/O方式只能建立大量的執行緒來保證每個使用者互不影響,到現在廣泛使用的I/O多路複用模型,再到非同步I/O;從select模型到現在主流的epoll模型,效能有了質的升級;當然我們沒必要自己去實現,可以直接使用網路通訊框架Netty,Mina等;

2.長連線短連線

短連線表示每次通訊完就關閉連線,而長連線通訊完繼續保持連線,這樣下次再通訊就不需要重新建立連線了,如果通訊頻繁,很明顯長連線效能更高;但是長連線需要做一些額外的工作,比如保活處理;另外就是如果客戶端太多的話,伺服器端是無法支撐的。

3.序列化方式

網路傳輸中的資料都需要經過序列化和反序列化處理,所以這一塊的效能也很重要;常見的序列化包括:json和二進位制方式,json常見的如fastjson,jackson等,二進位制如protobuf,thrift ,kryo等;這個可以分別從序列化的效能,大小,以及使用方便性考慮;當然穩定性和安全性也需要考慮,比如fastjson頻繁爆出安全漏洞;

4.協議

這裡主要講的是應用層協議,RPC一般都會自定義協議,當然也有直接使用現有協議的比如http協議;自定義協議可以自己掌控,協議可以做的很小很精簡,當然解碼和編碼需要自己去實現;如果使用現有的http協議,相對來說整個協議包是比較大的,但是已經是一種規範了,很多東西可以直接拿來用,更加通用;

可擴充套件性

可擴充套件性可以從兩個角度來看,一個是服務提供方和消費方的負載均衡策略;另一個就是使用者可以對框架進行自定義擴充套件;

1.負載均衡

RPC的兩個核心元件服務提供方和消費方,需要提供橫向擴充套件的機制,用以達到更高的負責,比如Spring Cloud、Dubbo提供的註冊中心,然後再結合容錯機制達到負載均衡的效果,很容易達到橫向擴充套件;

2.SPI機制

一個好的框架是支援使用者自定義的,使用者根據自己的需求實現自定義擴充套件;JDK提供了SPI機制,很常見的一個場景就是資料庫驅動;另外Dubbo在此基礎上提供了更加強大的SPI機制;這種擴充套件對RPC來說是多方面的,可以是底層的通訊框架擴充套件,序列化擴充套件,註冊中心擴充套件,容錯機制擴充套件,協議擴充套件等等;

跨平臺

現在開發語言多種多樣,如果能做成跨平臺,當然是一大優勢,但是可能往往為了跨平臺,會在一些地方做權衡讓步,當然難度也更大;所以我們在實現一個RPC時需要明確自己的定位,就是針對某一種語言的還是跨平臺支援;常見的支援跨平臺的RPC框架如GRPC,Thrift;
實現機制可以參考一下,都有自己的一套介面定義語言IDL然後通過不同的程式碼生成器,生成各種程式語言消費端和伺服器端程式碼,來保證不同語言直接的相關通訊。

功能性

作為一個RPC框架,除了最核心的通訊模組,序列化模組之外,功能性模組也很重要,往往為開發者節省了大量的時間,開發者可能因為RPC的某個功能而選擇用此框架;常見的功能包括:容錯機制,負載均衡機制,同步非同步呼叫,結果快取,路由規則,服務降級,多版本,執行緒模型等;

1.容錯機制

在錯綜複雜的網路環境中,遠端呼叫失敗再正常不過了,容錯機制就顯得非常有必要了,常見的容錯機制比如:失敗重試,快速失敗,失敗直接忽略,並行呼叫多個伺服器只要一個成功即返回等;使用者可以根據需求選擇自己合適的容錯方案;

2.負責均衡

上面提到服務提供方和消費方的可擴充套件性,消費方面對多個提供方的時候需要有一定的負載均衡策略,來保證系統的穩定性;常見的策略如:隨機,輪詢,最少活躍呼叫數,一致性hash等;

3.同步非同步呼叫

常見的同步呼叫有些場景無法滿足需求,比如同時需要呼叫多個遠端方法,而這其中可能有些執行比較慢的;這時候非同步呼叫就顯得重要了,可以同時非同步傳送多個請求,等待時間就是響應最慢的請求;具體可以通過Future,CompletableFuture來實現;

4.結果快取

快取一直是效能的不二法寶,某些場景下可能對服務提供方響應的資料實時要求性並不高,這時候如果可以在服務提供端提供結果的快取機制,那麼在效能上是一個很大的提升;可以自己設計一個本地快取,當然也可以直接整合第三方快取框架比如ehcache,jcache等;

5.路由規則

服務提供方往往是很多的,使用者可能有一些特殊的需求,可以按照自己定義個規則來做路由,比如我們經常做的灰度釋出,結合RPC提供的路由規則來實現會很簡單;此規則可能是條件表示式,指令碼,標籤等,我們設計的RPC框架需要有一個給使用者定義規則的地方,比如通過註冊中心來實時推送,另外還需要相關的引擎來處理規則,比如指令碼引擎;

6.服務降級

系統的高可用性原則中,很重要的一條就是降級處理,在一些非核心的功能中,可以在出現超時/故障時或者直接設定為降級服務,給一個統一的響應,這樣可以把寶貴的資源留給那些核心的功能;可以參考Dubbo的實現,向註冊中心寫入動態配置覆蓋規則,從而實現實時降級處理;

7.多版本

介面升級時常有的事情,我們可能會為了相容之前的版本絞盡腦汁,但有時候還是無能為力,這時候多版本就顯得很重要了,可以讓兩個版本同時存在,等到合適的機會在慢慢升級;像dubbo這種服務的維度就是服務名+版本號,所以很好實現多版本;而Spring Cloud維度沒有到版本號,可以通過路由規則去實現;

8.執行緒模型

在系統的高可用原則中,執行緒隔離是一條重要的原則,為什麼要做隔離,可以拿Dubbo及其底層通訊框架netty為例,netty作為通訊框架本身是有自己的執行緒模型,如果業務處理執行緒直接使用底層的通訊執行緒模型,這樣就會出現因為業務阻塞而導致通訊執行緒模型阻塞;這時Dubbo提供自己的執行緒模型就尤為重要,可以做到執行緒隔離,業務執行緒不影響通訊執行緒;

當然作為一個RPC框架實現的功能可以很多,這裡主要講一些我們平常用的比較多的功能;

可監控

一個執行穩定的系統,沒有一個專門的監控平臺是不行的,當然RPC也不例外,常見的比如dubbo的monitor模組,Spring Cloud Admin等;對於一個RPC我們主要監控:服務提供者有哪些,服務消費者有哪些,以及它們的狀態,最好還有一些統計功能比如一段時間內的呼叫量,成功率,失敗率,平均響應時間,最大響應時間,最大併發量等等;

使用性

最後說一下,我們設計出來的框架最終還是要給使用者使用的,所以使用者是否可以方便的使用也是一個很重要的點,某些框架可能就是因為使用繁瑣導致最終被棄用;比如註解的方式相比較xml的方式就簡單不少;還有比如服務維度來說:dubbo維度是介面,而Spring cloud維度是應用,整體來看Spring cloud使用起來更加方便;當然簡單的API,文件以及Demo對開發者來說也是必不可少的;

總結

RPC本質上其實就是一次網路呼叫,很多設計其實都是在圍繞,如何把它變成一個高可用,高併發的框架;其實這些設計理論適用於大部分的系統,都在為達到此目標而努力。

感謝關注

可以關注微信公眾號「 回滾吧程式碼」,第一時間閱讀,文章持續更新;專注Java原始碼、架構、演算法和麵試