從零寫分佈式RPC框架 系列 1.0 (1)架構設計

本系列文章的目的是搭建出一個基於Netty,Zookeeper和SpringBoot的簡易分佈式RPC框架,並且發佈到Maven中央倉庫以 spring-boot-starter 的形式對外提供開箱即用的服務。1.0 版本使用 protobuf 來做序列化,最終的使用形式比較接近於 Dubbo 。最終效果可見:https://github.com/linshenkx/rpc-netty-spring-boot-starter

系列文章:

項目GitHub地址:https://github.com/linshenkx/rpc-netty-spring-boot-starter

從零寫分佈式RPC框架 系列 1.0 (1)架構設計
從零寫分佈式RPC框架 系列 1.0 (2)RPC-Common模塊設計實現
從零寫分佈式RPC框架 系列 1.0 (3)RPC-Server模塊設計實現
從零寫分佈式RPC框架 系列 1.0 (4)RPC-Client模塊設計實現
從零寫分佈式RPC框架 系列 1.0 (5)整合測試
使用gpg插件發佈jar包到Maven中央倉庫 完整實踐

一 RPC設計

本RPC框架主要有RPC-Server 和 RPC-Client 兩個核心模塊。
對於使用這個框架的用戶來說,則有三個角色:Registry、rpc-provider、rpc-consumer。
本系列文章以搭建框架爲主,在最後的 examples 會給出結合 Registry、rpc-provider、rpc-consumer 的使用範例。

1 框架模塊

  • RPC-Server
    RPC服務器,通過自定義的協議而非http接口對外提供服務實現
    需要向註冊中心註冊服務信息和自身信息
  • RPC-Client
    RPC客戶端,通過向註冊中心訂閱服務,獲取提供對應服務實現的RPC Server信息,從而通過自定義協議從RPC Server獲取服務實現。

2 角色分工

  • Registry
    註冊中心,底層實現是Zookeeper,可藉助ZK集羣達到服務的高可用
  • rpc-provider
    服務提供者(服務生產者),依賴於RPC-Server模塊對外提供RPC服務
  • rpc-consumer
    服務消費者,依賴於RPC-Client模塊調用RPC服務

注意:本框架1.0版本未完善 RPC Client 和 Registry 的發佈訂閱模型,所以每次都需要 RPC Client去 Registry 獲取信息再 向 RPC Server 發起服務調用,也由此帶來極大的性能損耗和浪費。1.0版本只是實現了一個框架原型,後續版本將對性能等做優化。

設計

二 模塊結構

結構圖如下,其中 examples 模塊不依賴於父工程,可獨立運行,不屬於框架的一部分。
結構圖
rpc-netty-spring-boot-starter 下有5個模塊,其中外部使用的是兩個 starter ,而真正工作的事另外三個模塊。

  • rpc-netty-common
    封裝統一規則,如使RPC Server和RPC Client 可以基於同一協議通信。
    內含 自定義RPC通信對象及序列化方法,還有對應的Netty編碼器、解碼器等。
  • rpc-netty-server-spring-boot-autoconfigure
    • 核心類是RpcServer,負責提供RPC服務
    • 內含 ZKServiceRegistry 負責向zk集羣註冊服務,@RpcService 註解對外提供使用
    • 另外還有 RpcServerHandler 提供Netty 通信處理,ZKProperties 和 RpcServerProperties 提供屬性注入。
  • rpc-netty-server-spring-boot-starter
    對 rpc-netty-server-spring-boot-autoconfigure 進行包裝,本身無功能實現
  • rpc-netty-client-spring-boot-autoconfigure
    • 核心類是RpcClient,負責獲取服務實現生成代理類
    • 內含 ZKServiceDiscovery 負責發現指定服務的RPC Server信息
    • 另外還有 RpcClientHandler 提供Netty 通信處理,ZKProperties 提供屬性注入。
  • rpc-netty-client-spring-boot-starter
    對rpc-netty-client-spring-boot-autoconfigure 進行包裝,本身無功能實現

三 工作流程

RPC Server

服務啓動
掃描 RpcService 註解類
收集能實現的服務
在RpcServerProperties指定端口上 啓動Netty服務器
向zk集羣註冊服務和自身信息

需要注意,RPC Server需一直維持與zk集羣的連接,如果關閉RPC Server,zk上註冊的信息會隨之清除。

RPC Client

服務端利用反射執行方法生成結果
用戶觸發
獲取服務實現
傳遞接口信息
服務端返回結果

需要注意,這裏沒有使用發佈訂閱模型,沒有維持連接,所以每次使用都需要去獲取服務實現,沒有使用池化技術和緩存。需要在後續版本改良。

四 功能依賴

  • rpc-netty-common 模塊
    • protostuff-core :Rptostuff 核心模塊,提供對象序列化和反序列化功能
    • protostuff-runtime:Protostuff 運行時模塊,用於生成所需的 Protostuff Schema 對象
    • objenesis:提供比 JDK 更高效的反射功能,用於對象反序列化
  • rpc-netty-server-spring-boot-autoconfigure 和 rpc-netty-server-spring-boot-autoconfigure 模塊
    • spring-boot-starter:提供spring-boot 基礎服務,如 spring-context 信息等,該依賴scope 應爲 provided ,由使用方提供依賴,避免版本衝突
    • spring-boot-configuration-processor:提供屬性注入,scope同上
    • rpc-netty-common:提供統一服務
    • zkclient :提供對zk集羣的操作,注意應使用 exclusion 將其 slf4j-log4j12 模塊排除在classpath,否則會與spring-boot日誌框架重複或與Lombok的@log4j2衝突

除了上述依賴,還有以上3個模塊都依賴的

  • netty-all :Netty模塊,提供NIO通信所需的API,是整個RPC框架的基礎
  • lombok:其 optional 應爲true ,避免傳遞依賴

另外,兩個spring-boot-starter分別只依賴各自的spring-boot-autoconfigure模塊

注意,爲了避免依賴衝突問題,這裏使用到了 scope設爲provided,exclusion排除模塊, optional設爲true 三種方法。詳情請見:provided,optional 和 exclusion 最全區分指南

  1. scope設爲provided:對依賴整體有效,依賴將提供編譯而不參與打包,由使用方提供
    在本項目中,則是在發佈後,由使用 spring-boot-starter 的spring-boot工程提供。
  2. exclusion排除特定組件:避免傳遞依賴
    本項目中,用於移去 zkclient 的 slf4j-log4j12 模塊,避免依賴導入
  3. optional設爲true:避免傳遞依賴
    本項目中,rpc-netty-server-spring-boot-autoconfigure依賴optional爲true的 Lombok ,但 rpc-netty-server-spring-boot-starter則不含 Lombok 模塊,避免依賴傳出

五 父工程pom

這裏我把部署到maven 中央倉庫的內容刪去,不影響正常使用,感興趣的可以到GitHub找完整項目查看,部署的中央倉庫的方法見:使用gpg插件發佈jar包到Maven中央倉庫 完整實踐

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.github.linshenkx</groupId>
    <artifactId>rpc-netty-spring-boot-starter</artifactId>
    <version>1.0.5.RELEASE</version>
    <packaging>pom</packaging>

    <name>rpc-netty-spring-boot-starter</name>
    <description>基於Netty的簡易RPC框架</description>
    <url>https://github.com/linshenkx/rpc-netty-spring-boot-starter</url>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-boot.version>2.1.0.RELEASE</spring-boot.version>
        <spring-cloud.version>Finchley.SR2</spring-cloud.version>
        <netty.version>4.1.31.Final</netty.version>
        <protostuff.version>1.5.9</protostuff.version>
        <objenesis.version>3.0.1</objenesis.version>
        <zkclient.version>0.11</zkclient.version>
    </properties>

    <modules>
        <module>rpc-netty-common</module>
        <module>rpc-netty-server-spring-boot-autoconfigure</module>
        <module>rpc-netty-server-spring-boot-starter</module>
        <module>rpc-netty-client-spring-boot-autoconfigure</module>
        <module>rpc-netty-client-spring-boot-starter</module>
    </modules>
   
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
                <version>${spring-boot.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <version>${spring-boot.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>com.github.linshenkx</groupId>
                <artifactId>rpc-netty-common</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>com.github.linshenkx</groupId>
                <artifactId>rpc-netty-server-spring-boot-autoconfigure</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>com.github.linshenkx</groupId>
                <artifactId>rpc-netty-client-spring-boot-autoconfigure</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>io.netty</groupId>
                <artifactId>netty-all</artifactId>
                <version>${netty.version}</version>
            </dependency>
            <dependency>
                <groupId>io.protostuff</groupId>
                <artifactId>protostuff-core</artifactId>
                <version>${protostuff.version}</version>
            </dependency>
            <dependency>
                <groupId>io.protostuff</groupId>
                <artifactId>protostuff-runtime</artifactId>
                <version>${protostuff.version}</version>
            </dependency>
            <dependency>
                <groupId>org.objenesis</groupId>
                <artifactId>objenesis</artifactId>
                <version>${objenesis.version}</version>
            </dependency>
            <dependency>
                <groupId>com.101tec</groupId>
                <artifactId>zkclient</artifactId>
                <version>${zkclient.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.slf4j</groupId>
                        <artifactId>slf4j-log4j12</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
</project>

其他

參考資料:
項目GitHub主頁:https://github.com/linshenkx/rpc-netty-spring-boot-starter
《架構探險 輕量級微服務架構 下冊》黃勇 著,第四章-微服務通信
Dubbo GitHub主頁:https://github.com/apache/incubator-dubbo
https://github.com/luxiaoxun/NettyRpc
https://github.com/yidongnan/grpc-spring-boot-starter

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章