RPC 框架整體架構設計分析

RPC 就是把攔截到的方法參數,轉成可以在網絡中傳輸的二進制,並保證在服務提供方能正確地還原出語義,最終實現像調用本地一樣地調用遠程的目的。

1 RPC 架構

RPC 本質遠程調用,就要通過網絡來傳輸數據。考慮到可靠性,一般默認採用 TCP 協議。爲屏蔽網絡傳輸複雜性,要封裝一個單獨的數據傳輸模塊收發二進制數據,即傳輸模塊。

用戶請求是基於方法調用,方法出入參數都是對象數據,要提前轉成二進制,即序列化過程。但只是把方法調用參數的二進制數據傳輸到服務提供方不夠,要在方法調用參數的二進制數據後增加 “斷句” 符,分隔出不同的請求,在兩個 “斷句” 符號中間放的內容就是請求的二進制數據,即協議封裝。

這兩個不同過程目的一樣,保證數據在網絡中正確傳輸:

可把這兩個處理過程放在架構中的同一個模塊,統稱爲協議模塊。

還可在協議模塊加壓縮功能,壓縮過程也是對傳輸的二進制數據進行操作。在實際網絡傳輸過程中,請求數據包在數據鏈路層可能因太大而被拆分成多個數據包進行傳輸,爲減少被拆分次數,導致整個傳輸過程時間太長,可在 RPC 調用時:在方法調用參數或者返回值的二進制數據大於某個閾值的情況下,我們可以通過壓縮框架進行無損壓縮,然後在另外一端也用同樣的壓縮算法進行解壓,保證數據可還原。

傳輸和協議兩模塊是 RPC 最基礎功能,它們使對象可正確傳輸到服務提供方。但距離 RPC 目標——實現像調用本地一樣調用遠程,還缺點。要讓這兩個模塊同時工作,要手寫一些黏合代碼,但這些代碼對使用 RPC 的研發無意義,且屬於一個重複工作,導致使用體驗不友好。

要在 RPC 裏把這些細節對研發屏蔽,讓他們感覺不到本地調用和遠程調用區別。假設有用到 Spring,希望 RPC 能讓我們把一個 RPC 接口定義成一個 Spring Bean,並且這個 Bean 也會統一被 Spring Bean Factory 管理,可在項目中通過 Spring 依賴注入到方式引用。這是 RPC 調用的入口,一般叫 Bootstrap 模塊。

點對點(Point to Point)版本的 RPC 框架就完成了, 一般這種模式的 RPC 框架爲單機版,沒有集羣能力。

集羣能力:針對同一接口有多個服務提供者,但這多個服務提供者對調用方透明,所以在 RPC 裏還要給調用方找到所有的服務提供方,並在 RPC 裏維護好接口跟服務提供者地址的關係,調用方在發起請求時,才能快速找到對應接收地址,即 “服務發現”。

但服務發現只解決接口和服務提供方地址映射關係查找,是一種 “靜態數據”,對 RPC 來說,每次發送請求時都要用 TCP 連接的,相對服務提供方 IP 地址,TCP 連接狀態瞬息萬變,所以 RPC 框架要有連接管理器去維護 TCP 連接狀態。

有了集羣,提供方可能就需要管理好這些服務,RPC 就要內置一些服務治理功能,如服務提供方權重的設置、調用授權等一些常規治理手段。而服務調用方需要額外做哪些事?每次調用前,都要根據服務提供方設置的規則,從集羣中選擇可用的連接,以發送請求。

按分層設計原則,將這些功能模塊分爲:

圖片架構圖

2 可擴展架構

RPC 框架怎麼支持插件化架構?可將每個功能點抽象成一個接口,將這個接口作爲插件契約,然後把這個功能的接口與功能實現分離,並提供接口默認實現。

JDK 自帶 SPI 可動態爲某接口尋找服務實現,要在 Classpath 下的 META-INF/services 目錄創建一個以服務接口命名的文件,文件內容就是接口具體實現類。

JDK 自帶 SPI 的缺陷

不能按需加載,ServiceLoader 加載某接口實現類時,會遍歷全部獲取,即接口的實現類得全部載入並實例化,造成不必要浪費。

擴展如果依賴其它的擴展,就做不到自動注入和裝配,很難和其他框架集成,如擴展裏面依賴了一個 Spring Bean,原生 Java SPI 就不支持。

加上插件功能,RPC 框架就包含了兩大核心體系——核心功能體系與插件體系:

圖片插件化 RPC

整個架構就成了一個微內核架構,我們將每個功能點抽象成一個接口,將這個接口作爲插件的契約,然後把這個功能的接口與功能的實現分離並提供接口的默認實現。

這樣的架構可擴展性好,實現開閉原則,用戶方便通過插件擴展實現功能,而且不需要修改核心功能本身

保持了核心包的精簡,依賴外部包少,有效減少開發人員引入 RPC 導致的包版本衝突問題。

3 總結

我們都知道軟件開發的過程很複雜,不僅是因爲業務需求經常變化,更難的是在開發過程中要保證團隊成員的目標統一。我們需要用一種可溝通的話語、可 “觸摸” 的願景達成目標,我認爲這就是軟件架構設計的意義。

但僅從功能角度設計出的軟件架構並不夠健壯,系統不僅要能正確地運行,還要以最低的成本進行可持續的維護,因此我們十分有必要關注系統的可擴展性。只有這樣,才能滿足業務變化的需求,讓系統的生命力不斷延伸。

4 FAQ

① 使用 jmeter 進行壓力測試。jmeter 官網中支持進行定製 sampler 取樣器,寫好的 jar 包放在 lib\ext 下,再啓動 jmter 時就能看到了。

插件化是一個概念,有很多種實現方式,這種也算。

② spring 的 spring.factories 這一套也是利用的面向接口編程,感覺比 jdk 自帶的 spi 也好很多,既然有些問題,那爲啥 jdk 的 spi 不優化一下?

jdk 我理解更多是標準。

③ jdk 自帶 spi 一般會有一個接口加載很多實現類的情況嗎,因爲只能用迭代器遍歷,導致只能用類型判斷才能找到自己想要的類,這樣感覺不夠優雅吧,所以我感覺應該都是一個接口配置一個實現類這樣就是使用者想要的情況了。

那樣插件的意義就不存在了!

④ 業務爲工業設備聯網數據採集,設備種類和型號繁多,產品中通過抽象出一套 “驅動” 的概念,把每類設備當作一個插件開發,整體產品架構不變,感覺有點這個概念。只是產品還不夠大,其他插件體系還不夠明確。


本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/YQqRVd5uv4OjuCOUM2UNGw