文 / Kenyon,資深軟件架構師,15年軟件開發和技術管理經驗,從程序員做到企業技術高管,專注技術管理、架構設計、AI技術應用和落地。
由於公眾號推流的原因,請在關注頁右上角加星標,這樣才能及時收到新文章的推送。
引言
在上兩篇文章中,我們深入探討了架構設計的核心原則、方法和模式,從SOLID基本設計原則到分佈式系統的CAP定理,這些都是構建高質量系統的基礎。今天,我們將把這些理論知識應用到實踐中,讓我帶大家從零開始構建一個類似Dubbo的RPC框架。
RPC(Remote Procedure Call,遠程過程調用)是分佈式系統中服務之間通信最核心的機制,它允許一個進程去調用一個可以是遠在天邊的另外一個進程中的函數,雖然相隔甚遠,但是調用的時候就像調用本地函數一樣簡單。Dubbo作為國內最流行的RPC框架之一,它的設計理念和架構的實現充分體現了我們之前學習的各種架構設計原則。
一、核心需求分析
在開始設計之前,我們先明確一下我們的這個RPC框架的核心需求,根據YAGNI原則的要求,我們只實現當前必要的功能,主要是下面的5點:
- 具備遠程調用的能力:允許客户端像調用本地方法一樣調用遠程的服務實例
- 可以進行服務註冊與發現:客户端能夠自動發現可供調用的服務實例,無需手動去配置
- 具備負載均衡的能力:客户端或者服務端可以根據負載均衡的策略將請求分發到多個實例上面,提高系統可用性和性能
- 序列化與反序列化:客户端可以將對象轉換為可以進行網絡傳輸的格式,服務端再把對應的格式轉換為對象,並且支持跨語言調用
- 可以利用網絡進行通信:處理底層網絡傳輸,支持不同的協議(如TCP、HTTP)
二、基於架構設計原則的RPC架構設計
1. 基於SOLID原則的核心組件設計
單一職責原則(SRP)
因為遠程調用是RPC框架的核心功能,所以我們將RPC框架拆分為多個職責單一的組件:
- Proxy層:主要是負責將客户端的本地方法調用轉換為遠程調用
- Registry層:主要負責服務的註冊與發現,服務端可以在上面註冊自己的服務實例,客户端可以通過註冊中心發現可用的服務實例
- LoadBalance層:主要負責服務調用時的負載均衡策略,將客户端的請求分發到多個服務實例上面,提高系統的可用性和性能
- Serializer層:主要負責序列化與反序列化,將對象轉換為網絡傳輸格式,支持跨語言調用
- Transport層:負責網絡通信,處理底層的網絡傳輸,支持不同的協議(如TCP、HTTP等)
開閉原則(OCP)
通過抽象接口實現組件的可擴展性:
// 序列化接口,支持擴展不同的序列化方式(JSON、Protobuf等)
public interface Serializer {
<T> byte[] serialize(T obj);
<T> T deserialize(byte[] bytes, Class<T> clazz);
}
// 負載均衡接口,支持擴展不同的負載均衡策略(隨機、輪詢、一致性哈希等)
public interface LoadBalancer {
InetSocketAddress select(List<InetSocketAddress> addresses);
}
// 其他組件的SRP也類似,都是負責單一的功能
依賴倒置原則(DIP)
高層的模塊都不依賴底層模塊的具體實現,全都依賴其抽象類或接口:
- 比如我們在實現RPC框架核心模塊的邏輯的時候,都是依賴Serializer、LoadBalancer、RegistryCenter等抽象接口
- 像具體的實現類(如JsonSerializer、RandomLoadBalancer)都是依賴上面定義的這些接口
2. 基於通用設計原則的架構優化
高內聚低耦合
- 每個組件內部的功能和實現邏輯都是緊密相關的(高內聚)
- 組件之間通過接口來進行通信,儘量減少直接的依賴(低耦合)
- 例如:Proxy層不可以直接依賴具體的Transport的實現,而是通過Transport的接口來進行通信
KISS原則(Keep It Simple, Stupid)
- 初始的MVP版本只需要實現核心的功能就可以了,避免出現過度設計的情況
- 接口的設計要簡潔明瞭,減少不必要的參數和複雜的邏輯
- 例如:服務註冊只需要服務名稱、地址和端口即可,客户端只需要知道服務名稱即可調用
迪米特法則
- 組件之間只與直接依賴的組件通信
- 例如:客户端不需要知道服務註冊中心的具體實現,只需要通過Registry接口調用即可
三、RPC框架的核心架構圖
基於以上設計原則,我們可以畫出RPC框架的核心架構圖:
四、核心組件的詳細設計
1. 服務代理(Service Proxy)
- 該組件負責將客户端的本地方法調用轉換為遠程調用
- 實現方式:JDK動態代理、CGLIB代理
-
核心功能:
- 攔截方法調用
- 構建請求對象
- 選擇服務實例
- 發送遠程請求
- 處理響應結果
2. 服務註冊中心(Registry Center)
- 該組件負責客户端和服務端的服務註冊與發現
- 實現方式:可以實現基於ZooKeeper、Nacos等註冊中心來實現服務的註冊與發現
-
核心功能:
- 服務註冊:服務端將服務實例的信息註冊到註冊中心上面,主要的信息包括服務名稱、地址和端口等信息
- 服務發現:客户端從註冊中心獲取服務實例的列表,根據服務名稱來選擇需要調用的服務實例
- 服務監聽:監聽服務實例的上下線變化,及時更新本地的服務實例列表,以免調用到已經下線的服務實例
3. 負載均衡(Load Balance)
- 該組件負責將客户端的請求分發到服務端的實例上面進行處理,如果有多個實例的話,就可以根據指定的負載均衡算法來選擇其中一個實例進行處理
-
常見算法:
- 隨機算法(Random),在多個實例中隨機選擇一個進行處理
- 輪詢算法(Round Robin),按照服務端在註冊中心註冊時的順序依次選擇實例進行處理
- 一致性哈希算法(Consistent Hash),根據請求的哈希值來選擇實例進行處理,能夠保持請求的一致性
- 加權輪詢算法(Weighted Round Robin),根據實例的權重來選擇實例進行處理,權重高的實例會被更多的請求處理
4. 序列化(Serializer)
- 該組件負責將請求和處理結果的對象轉換成可以支持網絡傳輸的格式
-
常用序列化方式:
- JSON:可讀性好,支持跨語言處理
- Protobuf:性能高,空間效率好
- Hessian:Java原生支持,性能還算較好
5. 網絡傳輸(Transport)
- 該組件負責整個RPC框架底層的網絡通信
-
實現方式:
- Netty:一個基於異步事件驅動的高性能網絡通信框架
- Bio:同步阻塞IO,一般不建議使用
- Nio:同步非阻塞IO,性能較高,但是實現複雜
五、總結與下一步計劃
在上文中,我們基於SOLID原則和通用設計原則,設計了一個類似Dubbo的RPC框架的基礎架構。我們將RPC框架拆分為多個職責單一的組件,然後可以通過抽象接口的實現來擴展和替換這些組件,實現組件的可擴展性,並遵循了高內聚低耦合、KISS等設計原則。
在下一篇文章中,我們將進入實戰階段,實現RPC框架的核心功能,包括服務代理、序列化、網絡通信等模塊。
互動話題:你對RPC框架的設計有什麼看法?你認為RPC框架中最重要的組件是什麼?歡迎在評論區分享你的觀點。
關於作者
Kenyon,資深軟件架構師,15年的軟件開發和技術管理經驗,從程序員做到企業技術高管。多年企業數字化轉型和軟件架構設計經驗,善於幫助企業構建高質量、可維護的軟件系統,目前專注技術管理、架構設計、AI技術應用和落地;全網統一名稱"六邊形架構",歡迎關注交流。
原創不易,轉載請聯繫授權,如果覺得有幫助,請點贊、收藏、轉發三連支持!