您好,登錄后才能下訂單哦!
本篇內容介紹了“手寫一個RPC框架的方法教程”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
介紹
當開發一個單體項目的時候,大家肯定都寫過類似的代碼。即服務提供方和服務調用方在一個服務中
public interface HelloService { public String sayHello(String content); }
public class HelloServiceImpl implements HelloService { @Override public String sayHello(String content) { return "hello, " + content; } }
public class Test { public static void main(String[] args) { HelloService helloService = new HelloServiceImpl(); String msg = helloService.sayHello("world"); // hello world System.out.println(msg); } }
但是由于單體服務的諸多弊端,現在很多公司已經將不相關的功能拆分到不同的服務中。
如何像調用本地服務一樣調用遠程服務呢?這時就不得不提RPC框架了(Remote Procedure Call,遠程過程調用)。他幫我們屏蔽了網絡通信,序列化等操作的實現,真正做到了調用遠程服務和調用本地服務一樣方便。
知名的RPC框架有Spring Cloud,阿里巴巴的Dubbo,Facebook的Thrift,Google grpc等
RPC的調用過程
一個RPC調用的過程如下
鴻蒙官方戰略合作共建——HarmonyOS技術社區
調用方發送請求后由代理類將調用的方法,參數組裝成能進行網絡傳輸的消息體
調用方將消息體發送到提供方
提供方將消息進行解碼,得到調用的參數
提供方反射執行相應的方法,并將結果返回
下面我們就分析一下rpc框架是怎么實現的?有哪些地方可以擴展。為了讓大家有一個更形象的認識,我寫了一個github項目,由簡到難實現了一個rpc框架,歡迎star
https://github.com/erlieStar/simple-rpc
生成代理類
前面我們說過,調用方執行方法后,實際上執行的是代理類的方法,代理類幫我們進行序列化和編解碼操作。那么如何生成代理類呢?
我們看一下主流的做法。
Facebook的Thrift和Google的grpc都是定義一個schema文件,然后執行程序,幫你生成客戶端代理類,以及接口。調用方直接用生成的代理類來請求,提供方繼承生成的接口即可。
這種方式最大的優點就是能進行多語言通信,即一份schema文件可以生成Java程序,也可以生成Python程序。調用方是Java程序,提供方是Python程序都能正常進行通訊。而且是二進制協議,通訊效率比較高。
在Java中生成代理類的方式有如下幾種
鴻蒙官方戰略合作共建——HarmonyOS技術社區
JDK動態代理(實現InvocationHandler接口)
字節碼操作類庫(如cglib,Javassist)
在Dubbo中提供了2種生成代理類的方式,jdk動態代理和Javassist,默認是javassist,至于原因嗎?當然是javassist的效率更高
協議
為什么需要協議這個東西呢?Spring Cloud是通過Http協議來進行通訊的,那么Dubbo是通過哪種協議來進行通訊的?
為什么需要協議這個東西?
因為數據是以二進制的形式在網絡中傳輸中,RPC的請求數據并不是以一個整體發送到提供方的,而是可能被拆分成多個數據包發送出去,那提供方怎么識別數據呢?
例如一個文本ABCDEF,提供方有可能依次收到的數據為ABC DEF,也有可能為AB CD EF。提供方該怎么處理這些數據呢?
簡單啊,定個規則就可以了。這個規則可以有很多種,這里舉3個例子
鴻蒙官方戰略合作共建——HarmonyOS技術社區
定長協議,協議內容長度固定,如讀取到50個byte就開始decode操作,可以參考Netty的FixedLengthFrameDecoder
特殊結束符,定義一個消息結束的分隔符,如讀到\n,表示一個數據讀取完畢了,沒有讀到就一直讀,可以參考Netty的DelimiterBasedFrameDecoder
變長協議(協議頭+協議體),用一個定長來表示消息體的長度,剩下的內容為消息體,如果你愿意的話,協議頭還會放一些常用的屬性,Http協議的Header就是協議頭,如content-type,content-length等。可以參考Netty的DelimiterBasedFrameDecoder
Dubbo通過自定義協議來進行通訊,協議頭格式如下
每個位代表的含義如下
Dubbo為什么要自定義協議,而不用現成的Http協議?
最主要的原因就是自定義協議可以提高性能
Http協議的請求包比較大,有很多無用的內容。自定義協議可以精簡很多內容
Http協議是無狀態的,每次都要重新建立連接,響應完畢后將連接關閉
序列化
協議頭的內容是通過位來表示的,協議體在應用程序中則會被封裝成對象,如Dubbo將請求封裝成Request,將響應封裝成Response
前面我們說過網絡傳輸的數據必須是二進制數據,但調用方的入參和提供方的返回值都是對象,因此需要序列化和反序列化的過程
序列化的方式有如下幾種
鴻蒙官方戰略合作共建——HarmonyOS技術社區
JDK原生序列化
JSON
Protobuf
Kryo
Hessian2
MessagePack
我們選擇序列化的方式時,主要考慮如下幾個因素
鴻蒙官方戰略合作共建——HarmonyOS技術社區
效率
空間開銷
通用性和兼容性
安全性
通訊
常見的IO模型有如下四種
鴻蒙官方戰略合作共建——HarmonyOS技術社區
同步阻塞IO(Blocking IO)
同步非阻塞IO(Non-blocking IO)
IO多路復用(IO Multiplexing)
異步IO(Asynchronous IO)
因為RPC一般用在高并發的場景下,因此我們選擇IO多路復用這種模型,Netty的IO多路復用基于Reactor開發模式來實現,后續的文章我會分析一下這種開發模式是如何支持高并發的
注冊中心
注冊中心的作用和電話簿類似。保存了服務名稱和具體的服務地址之間的映射關系,當我們想和某個服務進行通信時,只需要根據服務名就能查到服務的地址。
更重要的是這個電話簿是動態的,當某個服務的地址改變時,電話簿上的地址就會改變,當某個服務不可用時,電話簿上的地址就會消失
這個動態的電話簿就是注冊中心。
注冊中心的實現方式有很多種,Zookeeper,Redis,Nocas等都可以實現
介紹一下用Zookeeper實現注冊中心的方式
zookeeper有兩種類型的節點,持久節點和臨時節點
當我們往zookeeper上注冊服務的時候,用的是臨時節點,這樣當服務斷開時,節點能被刪除
節點類型 | 解釋 |
---|---|
持久節點 | 將節點創建為持久節點,數據會一直存儲在zookeeper服務器上,即使創建該節點的客戶端與服務端的會話關閉了,該節點依然不會被刪除 |
持久順序節點 | 在持久節點的基礎上增加了節點有序的特性 |
臨時節點 | 將節點創建為臨時節點,數據不會一直存儲在zookeeper服務器上,當創建該臨時節點的客戶端會話關閉時,該節點在相應的zookeeper服務器上被刪除 |
臨時順序節點 | 在臨時節點的基礎上增加了節點有序的特性 |
注冊中心全部掛掉該怎么通信?
當一臺zookeeper掛掉后,會自動切換到另一個zookeeper。全部掛掉也沒有關系,因為dubbo把映射關系保存了一份在本地,這個映射關系可以保存在Map中,也可以保存在文件中
新的服務注冊到注冊中心,本地緩存會更新嗎?
注冊了監聽的話,當然會更新啊。當被監聽的節點或者子節點發生變化的時候,會將相應的內容推送給監聽的客戶端,你就可以更新本地的緩存了
Zookeeper中的事件如下
你可以把這個監聽理解為分布式的觀察者模式
“手寫一個RPC框架的方法教程”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。