一不小心實(shí)現(xiàn)了RPC

以下文章來源于公眾號(hào)crossoverJie ,作者crossoverJie

前言

image

隨著最近關(guān)注 cim 項(xiàng)目的人越發(fā)增多,導(dǎo)致提的問題以及 Bug 也在增加,在修復(fù)問題的過程中難免代碼潔癖又上來了。

看著一兩年前寫的東西總是懷疑這真的是出自自己手里嘛?有些地方實(shí)在忍不住了便開始了漫漫重構(gòu)之路。

前后對比

在開始之前先簡單介紹一下 cim 這個(gè)項(xiàng)目,下面是它的架構(gòu)圖:

image

簡單來說就是一個(gè) IM 即時(shí)通訊系統(tǒng),主要有以下部分組成:

  • IM-server 自然就是服務(wù)端了,用于和客戶端保持長連接。

  • IM-client 客戶端,可以簡單認(rèn)為是類似于的 QQ 這樣的客戶端工具;當(dāng)然功能肯定沒那么豐富,只提供了一些簡單消息發(fā)送、接收的功能。

  • Route 路由服務(wù),主要用于客戶端鑒權(quán)、消息的轉(zhuǎn)發(fā)等;提供一些 http 接口,可以用于查看系統(tǒng)狀態(tài)、在線人數(shù)等功能。

當(dāng)然服務(wù)端、路由都可以水平擴(kuò)展。


image

這是一個(gè)消息發(fā)送的流程圖,假設(shè)現(xiàn)在部署了兩個(gè)服務(wù)端 A、B 和一個(gè)路由服務(wù);其中 ClientAClientB 分別和服務(wù)端 A、B 保持了長連接。

當(dāng) ClientAClientB 發(fā)送一個(gè) hello world 時(shí),整個(gè)的消息流轉(zhuǎn)如圖所示:

  1. 先通過 http 將消息發(fā)送到 Route 服務(wù)。

  2. 路由服務(wù)得知 ClientB 是連接在 ServerB 上;于是再通過 http 將消息發(fā)送給 ServerB。

  3. 最終 ServerB 將消息通過與 ClientB 的長連接通道 push 下去,至此消息發(fā)送成功。

這里我截取了 ClientARoute 發(fā)起請求的代碼:

image

可以看到這就是利用 okhttp 發(fā)起了一個(gè) http 請求,這樣雖然能實(shí)現(xiàn)功能,但其實(shí)并不優(yōu)雅。

舉個(gè)例子:假設(shè)我們需要對接支付寶的接口,這里發(fā)送一個(gè) http 請求自然是沒問題;但對于支付寶內(nèi)部各部門直接互相調(diào)用接口時(shí)那就不應(yīng)該再使用原始的 http 請求了。

應(yīng)該是由服務(wù)提供方提供一個(gè) api 包,服務(wù)消費(fèi)者只需要依賴這個(gè)包就可以實(shí)現(xiàn)接口調(diào)用。

當(dāng)然最終使用的是 http、還是自定義私有協(xié)議都可以。

也類似于我們在使用 Dubbo 或者是 SpringCloud 時(shí),通常是直接依賴一個(gè) api 包,便可以像調(diào)用一個(gè)本地方法一樣調(diào)用遠(yuǎn)程服務(wù)了,并且完全屏蔽了底層細(xì)節(jié),不管是使用的 http 還是 其他私有協(xié)議都沒關(guān)系,對于調(diào)用者來說完全不關(guān)心。

這么一說是不是有內(nèi)味了,這不就是 RPC 的官方解釋嘛。

對應(yīng)到這里也是同樣的道理, Client 、 Route、 Server 本質(zhì)上都是一個(gè)系統(tǒng),他們互相的接口調(diào)用也應(yīng)當(dāng)是走 RPC 才合理。

所以我重構(gòu)之后的變成這樣了:

image

是不是代碼也簡潔了許多,就和調(diào)用本地方法一樣了,而且這樣也有幾個(gè)好處:

  • 完全屏蔽了底層細(xì)節(jié),可以更好的實(shí)現(xiàn)業(yè)務(wù)及維護(hù)代碼。

  • 即便是服務(wù)提供方修改了參數(shù),在編譯期間就能很快發(fā)現(xiàn),而像之前那樣調(diào)用是完全不知情的,所以也增加了風(fēng)險(xiǎn)。

繞不開的動(dòng)態(tài)代理

下面來聊聊具體是如何實(shí)現(xiàn)的。

要想做到對調(diào)用者無感知,就得創(chuàng)建一個(gè)接口的代理對象;在這個(gè)代理對象中實(shí)現(xiàn)編碼、調(diào)用、解碼的過程。

image

對應(yīng)到此處其實(shí)就是創(chuàng)建一個(gè) routeApi 的代理對象,關(guān)鍵就是這段代碼:

  1. RouteApi routeApi = new ProxyManager<>(RouteApi.class, routeUrl, okHttpClient).getInstance();

完整源碼如下:

image

其中的 getInstance() 函數(shù)就是返回了需要被代理的接口對象;而其中的 ProxyInvocation則是一個(gè)實(shí)現(xiàn)了 InvocationHandler 接口的類,這套代碼就是利用 JDK 實(shí)現(xiàn)動(dòng)態(tài)代理的三板斧。

image

查看 ProxyInvocation 的源碼會(huì)發(fā)現(xiàn)當(dāng)我們調(diào)用被代理接口的任意一個(gè)方法時(shí),都會(huì)執(zhí)行這里的 invoke()方法。

invoke() 方法自然就實(shí)現(xiàn)了上圖中提到的:編碼、遠(yuǎn)程調(diào)用、解碼的過程;相信大家很容易看明白,由于不是本次探討的重點(diǎn)就不過多介紹了。

總結(jié)

其實(shí)理解這些就也就很容易看懂 Dubbo 這類 RPC 框架的核心源碼了,總體的思路也是類似的,只不過使用的私有協(xié)議,所以在編解碼時(shí)會(huì)有所不同。

所以大家要是想自己動(dòng)手實(shí)現(xiàn)一個(gè) RPC 框架,不妨參考這個(gè)思路試試,當(dāng)用自己寫的代碼跑通一個(gè) RPChelloworld 時(shí)的感覺是和自己整合了一個(gè) Dubbo、 SpringCloud 這樣的第三方框架的感覺是完全不同的。

本文的所有源碼:

https://github.com/crossoverJie/cim

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容