前言
隨着最近關注 cim 項目的人越發增多,導致提的問題以及 Bug 也在增加,在修復問題的過程中難免代碼潔癖又上來了。
看着一兩年前寫的東西總是懷疑這真的是出自自己手里嘛?有些地方實在忍不住了便開始了漫漫重構之路。
前后對比
在開始之前先簡單介紹一下 cim
這個項目,下面是它的架構圖:
簡單來說就是一個 IM 即時通訊系統,主要有以下部分組成:
IM-server
自然就是服務端了,用於和客戶端保持長連接。IM-client
客戶端,可以簡單認為是類似於的 QQ 這樣的客戶端工具;當然功能肯定沒那么豐富,只提供了一些簡單消息發送、接收的功能。Route
路由服務,主要用於客戶端鑒權、消息的轉發等;提供一些 http 接口,可以用於查看系統狀態、在線人數等功能。
當然服務端、路由都可以水平擴展。
這是一個消息發送的流程圖,假設現在部署了兩個服務端 A、B 和一個路由服務;其中 ClientA
和 ClientB
分別和服務端 A、B 保持了長連接。
當 ClientA
向 ClientB
發送一個 hello world
時,整個的消息流轉如圖所示:
- 先通過
http
將消息發送到Route
服務。 - 路由服務得知
ClientB
是連接在ServerB
上;於是再通過http
將消息發送給ServerB
。 - 最終
ServerB
將消息通過與ClientB
的長連接通道push
下去,至此消息發送成功。
這里我截取了 ClientA
向 Route
發起請求的代碼:
可以看到這就是利用 okhttp
發起了一個 http
請求,這樣雖然能實現功能,但其實並不優雅。
舉個例子:假設我們需要對接支付寶的接口,這里發送一個 http 請求自然是沒問題;但對於支付寶內部各部門直接互相調用接口時那就不應該再使用原始的 http 請求了。
應該是由服務提供方提供一個 api
包,服務消費者只需要依賴這個包就可以實現接口調用。
當然最終使用的是 http、還是自定義私有協議都可以。
也類似於我們在使用 Dubbo
或者是 SpringCloud
時,通常是直接依賴一個 api
包,便可以像調用一個本地方法一樣調用遠程服務了,並且完全屏蔽了底層細節,不管是使用的 http 還是 其他私有協議都沒關系,對於調用者來說完全不關心。
這么一說是不是有內味了,這不就是 RPC 的官方解釋嘛。
對應到這里也是同樣的道理,Client
、Route
、Server
本質上都是一個系統,他們互相的接口調用也應當是走 RPC
才合理。
所以我重構之后的變成這樣了:
是不是代碼也簡潔了許多,就和調用本地方法一樣了,而且這樣也有幾個好處:
- 完全屏蔽了底層細節,可以更好的實現業務及維護代碼。
- 即便是服務提供方修改了參數,在編譯期間就能很快發現,而像之前那樣調用是完全不知情的,所以也增加了風險。
繞不開的動態代理
下面來聊聊具體是如何實現的。
其實在上文《動態代理的實際應用》 中也有講到,原理是類似的。
要想做到對調用者無感知,就得創建一個接口的代理對象;在這個代理對象中實現編碼、調用、解碼的過程。
對應到此處其實就是創建一個 routeApi
的代理對象,關鍵就是這段代碼:
RouteApi routeApi = new ProxyManager<>(RouteApi.class, routeUrl, okHttpClient).getInstance();
完整源碼如下:
其中的 getInstance()
函數就是返回了需要被代理的接口對象;而其中的 ProxyInvocation
則是一個實現了 InvocationHandler
接口的類,這套代碼就是利用 JDK
實現動態代理的三板斧。
查看 ProxyInvocation
的源碼會發現當我們調用被代理接口的任意一個方法時,都會執行這里的 invoke()
方法。
而 invoke()
方法自然就實現了上圖中提到的:編碼、遠程調用、解碼的過程;相信大家很容易看明白,由於不是本次探討的重點就不過多介紹了。
總結
其實理解這些就也就很容易看懂 Dubbo
這類 RPC
框架的核心源碼了,總體的思路也是類似的,只不過使用的私有協議,所以在編解碼時會有所不同。
所以大家要是想自己動手實現一個 RPC
框架,不妨參考這個思路試試,當用自己寫的代碼跑通一個 RPC
的 helloworld
時的感覺是和自己整合了一個 Dubbo
、SpringCloud
這樣的第三方框架的感覺是完全不同的。
本文的所有源碼:
https://github.com/crossoverJie/cim
你的點贊與分享是對我最大的支持