-
1 # 深漂小馬哥
-
2 # java架構設計
RPC框架全稱叫“Remote Procedure Call”-遠端過程呼叫。
在分散式應用大放異彩的今天,軟體研發工作(微服務、中臺)是一定離不開分散式,既然說到分散式就一定離不開RPC,所以RPC框架也是廣大程式設計師進階路上必須跨過去的一道坎,也是工作中經常會用到的。因此,瞭解並掌握RPC框架的底層原理和其設計思想也是每一個程式設計師的必備技能之一。
業界知名RPC各大網際網路公司都或多或少實現了自己的RPC框架,其中比較出名且常用的有:
阿里巴巴的dubbo:這個大家應該都用到過吧?GitHub地址:https://github.com/apache/dubbo
基於阿里巴巴fork的當當版dubbox:這個貌似也都用到過?在dubbo不死不活的那幾年。GitHub地址:https://github.com/dangdangdotcom/dubbox
谷歌grpc:谷歌版本的rpc,GitHub地址(Java版):https://github.com/grpc/grpc-java
大家可以自己去GitHub上去搜索這三大框架並進行學習。
自己簡單實現一個最基本的RPC框架如果我們自己去實現一個簡單的RPC框架要怎麼做?這個時候我們應該意識到我們是在寫一個框架,寫一個框架意味著我們無法再隨意的使用第三方依賴包,也就是除了一些工具類包可以使用,其他的都需要我們用Java一行一行的寫出來。
下面提供一下如何用Java實現一個簡單的RPC功能(談不上框架):
先定義一個RPC介面類,程式碼如下圖:
寫Rpc介面類的實現類實現這兩個方法,export方法如下圖:
refer方法:
框架定義完成後,我們開始使用這個框架,先簡單定義一個測試用的服務介面:
實現類:
我們需要寫一個provider把這個服務提供出去,執行這個main方法即可:
寫一個cosumer遠端消費這個服務:
執行這個main方法會輸出:
這樣一個簡單的RPC遠端過程呼叫就實現了,是不是很簡單?採用Java Socket套接字程式設計和Java自帶的序列化。
設計一個RPC框架需要我們掌握哪些知識?上面簡單實現了一個RPC框架,這個只是實驗性質的編碼,實際上是無法使用的,因為在實際業務中,我們面對的是高併發、多請求。上面的套接字程式設計也是阻塞的,另外序列化也是用的Java自帶的序列化方法。
而瞭解主流rpc實現原理的同學都知道,每一個rpc框架在效能的考慮上都做到了自己框架的極致,框架本身既要考慮高可擴充套件的同時又得考慮高效能。
比如在dubbo中,預設使用hessian序列化的方式傳輸網路資料,同時也實現了主流的集中序列化方式,比如Java自帶的序列化、Kryo序列化、fastJson序列化等方式。底層採用netty去做網路通訊使用非阻塞NIO。註冊中心既有zookeepeer,也支援redis。動態代理採用Javassist等等技術。
所以我們在設計一個rpc框架的時候,首先必須掌握上面說的那些知識,還得要求我們能夠很好的結合每一個技術,做到融匯貫通。
網上也有很多關於RPC相關知識的文章,也有dubbo、dubbox、grpc等rpc框架的原始碼解析文章,建議大家可以深耕這一塊,徹底摸透掌握這塊的實現原理。
回覆列表
RPC框架的核心目標是解決分散式系統中服務之間的呼叫問題。
RPC框架三個核心角色
1)服務提供者(Server)對外提供後臺服務,將自己的服務資訊,註冊到註冊中心
2)註冊中心(Registry)用於服務端註冊遠端服務以及客戶端發現服務。
3)服務消費者(Client) 從註冊中心獲取遠端服務的註冊資訊,然後進行遠端過程呼叫。
RPC遠端呼叫過程
1)服務呼叫方(client)呼叫以本地呼叫方式呼叫服務;
2)client stub接收到呼叫後負責將方法、引數等組裝成能夠進行網路傳輸的訊息體;在Java裡就是序列化的過程;
3)client stub找到服務地址,並將訊息透過網路傳送到服務端;
4)server stub收到訊息後進行解碼,在Java裡就是反序列化的過程;
5)server stub根據解碼結果呼叫本地的服務;
6)本地服務執行處理邏輯;
7)本地服務將結果返回給server stub;
8)server stub將返回結果打包成訊息,Java裡的序列化;
9)server stub將打包後的訊息透過網路併發送至消費方;
10)client stub接收到訊息,並進行解碼, Java裡的反序列化;
11)服務呼叫方(client)得到最終結果。
RPC框架的目標就是要2~10這些步驟都封裝起來。
RPC框架涉及技術
a.建立通訊
首先,要解決通訊的問題,主要是透過在客戶端和伺服器之間建立TCP連線,遠端過程呼叫的所有交換的資料都在這個連線裡傳輸。
當前很多RPC框架都直接基於netty這一IO通訊框架,推薦使用Netty 作為底層通訊框架。
b.網路傳輸
資料傳輸採用什麼協議(二進位制資料格式組織)?
資料該如何序列化和反序列化?(kryo/protobuf/protostuff/hessian/fastjson/…)
c.服務定址
1)服務註冊
服務提供者啟動後主動把服務註冊到服務中心,註冊中心儲存了該服務的IP、埠、呼叫方式(協議、序列化方式)等資訊。
2)服務發現
服務消費者第一次呼叫服務時,會透過註冊中心找到相應的服務提供方地址列表,並快取到本地,以供後續使用。當消費者再次呼叫服務時,不會再去請求註冊中心,而是直接透過負載均衡演算法從IP列表中取一個服務提供者的伺服器呼叫服務。
d.服務呼叫
服務消費者進行本地呼叫(透過代理Proxy)之後得到了返回值。實際上是在Proxy中封裝了一系列的過程,包括序列化、請求服務提供者、反序列化等等。
建立Proxy的方式?(jdk proxy/javassist/cglib/asm/bytebuddy)
Proxy還能做什麼?軟負載均衡(加權隨機、加權輪詢、最小負載、一致性Hash…) 、叢集容錯(Fail-fast、Failover、Fail-safe、Fail-back)、同步/非同步呼叫、流控、熔斷、降級、限流、隔離和超時…(服務治理)
實現高可用RPC框架需要考慮到的問題
要實現一個RPC不算難,難的是實現一個高效能高可靠的RPC框架。
既然系統採用分散式架構,那一個服務勢必會有多個例項,要解決如何獲取例項的問題?
如何選擇例項呢?就要考慮負載均衡
如果每次都去註冊中心查詢列表,效率很低,那麼就要加快取
客戶端總不能每次呼叫完都等著服務端返回資料,所以就要支援非同步呼叫
服務端的介面修改了,老的介面還有人在用,這就需要版本控制;
服務端總不能每次接到請求都馬上啟動一個執行緒去處理,於是就需要執行緒池;
Failover - 失敗自動切換,當出現失敗,重試其它伺服器。通常用於讀操作,但重試會帶來更長延遲。可透過 retries=“2” 來設定重試次數(不含第一次)。
Failfast - 快速失敗,只發起一次呼叫,失敗立即報錯。通常用於非冪等性的寫操作,比如新增記錄。
Failsafe - 失敗安全,出現異常時,直接忽略。通常用於寫入審計日誌等操作。
Failback - 失敗自動恢復,後臺記錄失敗請求,定時重發。通常用於訊息通知操作。
Forking - 並行呼叫多個伺服器,只要一個成功即返回。通常用於實時性要求較高的讀操作,但需要浪費更多服務資源。可透過 forks=“2” 來設定最大並行數。
Broadcast - 播呼叫所有提供者,逐個呼叫,任意一臺報錯則報錯。通常用於通知所有提供者更新快取或日誌等本地資源資訊