前言
隨著最近關注 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
你的點贊與分享是對我最大的支援