手写 RPC 框架

一、RPC 是什么?为什么需要 RPC?

在分布式系统中,我们经常需要调用其他服务器上的方法,比如用户服务需要调用订单服务的 "创建订单" 接口。如果直接通过 HTTP 调用,会面临序列化、网络通信、服务发现、负载均衡等一系列问题。RPC(Remote Procedure Call,远程过程调用) 就是为解决这些问题而生的技术 ------ 它能让我们像调用本地方法一样调用远程服务,屏蔽分布式通信的复杂细节。

举个通俗的例子:你在公司电脑上想查看家里电脑的文件,不需要自己手动建立网络连接、处理数据传输,只需要双击 "远程桌面" 图标,就能像操作本地电脑一样操作家里的电脑。RPC 就相当于分布式系统中的 "远程桌面",让跨服务调用变得简单。

1.1 RPC 的核心价值

  • 透明化远程调用:调用远程方法和调用本地方法语法一致,开发者无需关注网络通信细节。
  • 高性能通信:相比 HTTP,RPC 通常采用更轻量的协议(如自定义 TCP 协议)和更高效的序列化方式(如 Protobuf、FastJSON2),性能更高。
  • 服务化支撑:RPC 框架通常集成服务注册发现、负载均衡、容错等能力,是微服务架构的基础。

1.2 RPC 调用的核心流程

为了让大家直观理解 RPC 的工作原理,我们用 Mermaid 流程图展示一次完整的 RPC 调用过程:

二、RPC 框架的核心架构设计

一个成熟的 RPC 框架需要包含 6 大核心组件,各组件职责清晰、协同工作。我们先通过架构图了解整体结构:

各组件的核心职责如下:

  1. 服务注册中心:存储服务名称与服务地址的映射关系(如 ZooKeeper、Nacos),支持服务注册和发现。
  2. 服务提供者:暴露本地服务,将服务信息注册到注册中心,接收并处理消费者的远程调用请求。
  3. 服务消费者:从注册中心获取服务地址,通过动态代理发起远程调用。
  4. 动态代理层:为消费者生成服务代理对象,将 "调用代理方法" 转为 "远程调用请求"。
  5. 网络传输层:负责跨节点的二进制数据传输(通常基于 Netty 的 NIO 模型实现)。
  6. 序列化层:将 Java 对象与二进制数据相互转换(解决 "对象不能跨网络传输" 的问题)。

三、环境搭建:确定技术栈与依赖

在开始编码前,我们先明确技术栈选型,所有组件均采用最新稳定版本,确保兼容性和性能:

组件 技术选型 版本 用途说明
JDK Oracle JDK 17 基础开发环境
项目管理 Maven 3.9.6 依赖管理和项目构建
网络通信 Netty 4.1.100.Final 高性能 NIO 通信框架,实现 TCP 传输
服务注册中心 ZooKeeper 3.9.2 存储服务地址,支持服务发现
序列化 FastJSON2 2.0.48 高效 JSON 序列化 / 反序列化框架
动态代理 JDK 动态代理 内置 生成服务代理对象
工具类 Lombok、Spring Utils Lombok 1.18.30 简化代码(@Slf4j)、判空工具等
接口文档 Swagger3 2.2.0 生成 API 文档,方便测试
持久层(可选) MyBatis-Plus 3.5.5 若服务需要操作数据库,用于数据访问

3.1 核心依赖配置(pom.xml)

创建 Maven 项目,在pom.xml中添加以下依赖,所有版本均为 2024 年最新稳定版:

复制代码
<?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.ken.rpc</groupId>
    <artifactId>ken-rpc-framework</artifactId>
    <version>1.0.0</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- 依赖版本管理 -->
        <netty.version>4.1.100.Final</netty.version>
        <zookeeper.version>3.9.2</zookeeper.version>
        <fastjson2.version>2.0.48</fastjson2.version>
        <lombok.version>1.18.30</lombok.version>
        <spring-context.version>6.1.5</spring-context.version>
        <swagger.version>2.2.0</swagger.version>
        <mybatis-plus.version>3.5.5</mybatis-plus.version>
        <mysql.version>8.0.36</mysql.version>
    </properties>

    <dependencies>
        <!-- 1. 基础工具类 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring-context.version}</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>33.1.0-jre</version>
        </dependency>

        <!-- 2. 网络通信:Netty -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>${netty.version}</version>
        </dependency>

        <!-- 3. 服务注册中心:ZooKeeper -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>${zookeeper.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- 4. 序列化:FastJSON2 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>${fastjson2.version}</version>
        </dependency>

        <!-- 5. 接口文档:Swagger3 -->
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${swagger.version}</version>
        </dependency>

        <!-- 6. 持久层:MyBatis-Plus + MySQL(服务实现需操作数据库时使用) -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
</project>

四、核心模块实现:从底层到上层

我们按照 "底层支撑→核心逻辑→上层应用" 的顺序实现 RPC 框架,每个模块都包含 "设计思路 + 可运行代码",确保读者能直接复用。

4.1 序列化层:解决 "对象跨网络传输" 问题

设计思路:Java 对象不能直接通过网络传输,需要先转为二进制数据(序列化);服务端接收后,再将二进制转成 Java 对象(反序列化)。我们选择 FastJSON2 作为序列化工具(相比 JSON、Protobuf,它兼顾性能和易用性),定义统一的序列化接口,方便后续替换其他序列化方式。

4.1.1 序列化接口定义
复制代码
package com.ken.rpc.serialize;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.ken.rpc.exception.SerializeException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;

/**
 * 序列化工具类(基于FastJSON2实现)
 * 负责Java对象与二进制数据的相互转换
 *
 * @author ken
 */
@Slf4j
public class FastJson2Serializer implements Serializer {

    /**
     * 序列化:将Java对象转为字节数组
     *
     * @param obj 待序列化的对象
     * @return 序列化后的字节数组
     * @throws SerializeException 序列化失败时抛出
     */
    @Override
    public byte[] serialize(Object obj) throws SerializeException {
        if (ObjectUtils.isEmpty(obj)) {
            log.error("序列化对象为空,无法执行序列化操作");
            throw new SerializeException("序列化对象不能为空");
        }
        try {
            // FastJSON2序列化:对象→JSON字符串→字节数组(指定UTF-8编码)
            return JSON.toJSONBytes(obj);
        } catch (Exception e) {
            log.error("对象序列化失败,对象类型:{},异常信息:{}", obj.getClass().getName(), e.getMessage(), e);
            throw new SerializeException("序列化失败:" + e.getMessage());
        }
    }

    /**
     * 反序列化:将字节数组转为指定类型的Java对象
     *
     * @param bytes 待反序列化的字节数组
     * @param clazz 目标对象的Class类型
     * @param <T>   目标对象的泛型
     * @return 反序列化后的Java对象
     * @throws SerializeException 反序列化失败时抛出
     */
    @Override
    public <T> T deserialize(byte[] bytes, Class<T> clazz) throws SerializeException {
        if (ObjectUtils.isEmpty(bytes)) {
            log.error("反序列化字节数组为空,无法执行反序列化操作");
            throw new SerializeException("反序列化字节数组不能为空");
        }
        if (ObjectUtils.isEmpty(clazz)) {
            log.error("反序列化目标类型为空,无法执行反序列化操作");
            throw new SerializeException("反序列化目标类型不能为空");
        }
        try {
            // FastJSON2反序列化:字节数组→指定类型对象
            return JSON.parseObject(bytes, clazz);
        } catch (Exception e) {
            log.error("字节数组反序列化失败,目标类型:{},异常信息:{}", clazz.getName(), e.getMessage(), e);
            throw new SerializeException("反序列化失败:" + e.getMessage());
        }
    }

    /**
     * 反序列化(支持泛型类型,如List<User>、Map<String, Object>)
     *
     * @param bytes          待反序列化的字节数组
     * @param typeReference FastJSON2的TypeReference,用于指定泛型类型
     * @param <T>           目标对象的泛型
     * @return 反序列化后的Java对象
     * @throws SerializeException 反序列化失败时抛出
     */
    @Override
    public <T> T deserialize(byte[] bytes, TypeReference<T> typeReference) throws SerializeException {
        if (ObjectUtils.isEmpty(bytes)) {
            log.error("反序列化字节数组为空,无法执行反序列化操作");
            throw new SerializeException("反序列化字节数组不能为空");
        }
        if (ObjectUtils.isEmpty(typeReference)) {
            log.error("反序列化泛型类型为空,无法执行反序列化操作");
            throw new SerializeException("反序列化泛型类型不能为空");
        }
        try {
            // 支持泛型的反序列化(如List<User>)
            return JSON.parseObject(bytes, typeReference);
        } catch (Exception e) {
            log.error("字节数组泛型反序列化失败,异常信息:{}", e.getMessage(), e);
            throw new SerializeException("泛型反序列化失败:" + e.getMessage());
        }
    }
}
4.1.2 序列化异常定义

为了统一异常处理,定义序列化相关的自定义异常:

复制代码
package com.ken.rpc.exception;

/**
 * 序列化/反序列化异常
 * 当对象与字节数组转换过程中出现错误时抛出
 *
 * @author ken
 */
public class SerializeException extends RuntimeException {

    public SerializeException(String message) {
        super(message);
    }

    public SerializeException(String message, Throwable cause) {
        super(message, cause);
    }
}

4.2 网络传输层:基于 Netty 实现高性能通信

设计思路:网络传输是 RPC 的核心底层支撑,需要解决 "高并发""低延迟" 问题。Netty 是基于 NIO 的高性能通信框架,我们用它实现 TCP 服务端(服务提供者)和客户端(服务消费者),并定义统一的 "请求 / 响应" 数据结构,确保数据传输的完整性。

4.2.1 定义 RPC 请求 / 响应实体

首先定义 RPC 调用的 "请求" 和 "响应" 格式,所有网络传输的数据都遵循这个结构:

复制代码
package com.ken.rpc.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * RPC请求实体
 * 封装消费者向提供者发送的调用信息
 *
 * @author ken
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RpcRequest implements Serializable {

    /**
     * 请求ID:唯一标识一次RPC调用(用于解决请求与响应的对应问题)
     */
    private String requestId;

    /**
     * 服务接口名称(如com.ken.rpc.service.UserService)
     * 用于服务提供者找到对应的实现类
     */
    private String serviceName;

    /**
     * 方法名称(如getUserById)
     */
    private String methodName;

    /**
     * 方法参数类型列表(如[java.lang.Long])
     * 用于服务提供者找到重载的方法
     */
    private Class<?>[] parameterTypes;

    /**
     * 方法参数值列表(如[1001])
     */
    private Object[] parameters;

    /**
     * 服务版本号(用于处理服务升级,如1.0.0、2.0.0)
     */
    private String serviceVersion;
}

package com.ken.rpc.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * RPC响应实体
 * 封装服务提供者向消费者返回的结果
 *
 * @author ken
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RpcResponse implements Serializable {

    /**
     * 请求ID:与RpcRequest的requestId对应,确保响应匹配
     */
    private String requestId;

    /**
     * 调用成功:返回的方法执行结果
     */
    private Object result;

    /**
     * 调用失败:返回的异常信息
     */
    private Throwable exception;

    /**
     * 判断本次RPC调用是否成功
     *
     * @return true-成功,false-失败
     */
    public boolean isSuccess() {
        return exception == null;
    }
}
4.2.2 Netty 服务端实现(服务提供者)

服务提供者需要启动 Netty 服务端,监听指定端口,接收消费者的请求并处理:

复制代码
package com.ken.rpc.transport.netty;

import com.ken.rpc.entity.RpcRequest;
import com.ken.rpc.entity.RpcResponse;
import com.ken.rpc.handler.RpcRequestHandler;
import com.ken.rpc.serialize.FastJson2Serializer;
import com.ken.rpc.serialize.Serializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * Netty RPC服务端
 * 负责启动服务、监听端口、接收并处理消费者的RPC请求
 *
 * @author ken
 */
@Slf4j
public class NettyRpcServer {

    /**
     * 服务端口号
     */
    private final int port;

    /**
     * 服务注册中心地址(如zookeeper://127.0.0.1:2181)
     */
    private final String registryAddress;

    /**
     * 序列化工具(默认使用FastJSON2)
     */
    private final Serializer serializer;

    /**
     * RPC请求处理器(负责调用本地服务方法)
     */
    private final RpcRequestHandler rpcRequestHandler;

    public NettyRpcServer(int port, String registryAddress) {
        this(port, registryAddress, new FastJson2Serializer(), new RpcRequestHandler());
    }

    public NettyRpcServer(int port, String registryAddress, Serializer serializer, RpcRequestHandler rpcRequestHandler) {
        this.port = port;
        this.registryAddress = registryAddress;
        this.serializer = serializer;
        this.rpcRequestHandler = rpcRequestHandler;
    }

    /**
     * 启动Netty服务端
     */
    public void start() {
        // 1. 校验参数
        if (port <= 0 || port > 65535) {
            log.error("服务端口号非法,端口:{}(合法范围:1-65535)", port);
            throw new IllegalArgumentException("服务端口号非法:" + port);
        }
        if (!StringUtils.hasText(registryAddress)) {
            log.error("服务注册中心地址为空,无法启动服务");
            throw new IllegalArgumentException("服务注册中心地址不能为空");
        }

        // 2. 初始化Netty线程组
        // BossGroup:负责接收客户端连接
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // WorkerGroup:负责处理客户端的IO请求
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            // 3. 配置服务端启动参数
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    // 配置服务端通道类型(NIO)
                    .channel(NioServerSocketChannel.class)
                    // 日志打印(DEBUG级别,方便调试)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    // 配置客户端连接的通道初始化器
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();

                            // ① 粘包/拆包处理器:基于消息长度字段解决TCP粘包问题
                            // 解码:读取消息时,先读取4字节的长度字段,再读取对应长度的消息体
                            pipeline.addLast(new LengthFieldBasedFrameDecoder(
                                    Integer.MAX_VALUE, 0, 4, 0, 4));
                            // 编码:发送消息时,在消息体前添加4字节的长度字段
                            pipeline.addLast(new LengthFieldPrepender(4));

                            // ② 序列化/反序列化处理器:将字节数组转为Java对象(请求),将Java对象转为字节数组(响应)
                            pipeline.addLast(new NettySerializerHandler(serializer, RpcRequest.class, RpcResponse.class));

                            // ③ RPC请求处理器:处理请求,调用本地服务方法,生成响应
                            pipeline.addLast(new NettyRpcServerHandler(rpcRequestHandler));
                        }
                    })
                    // 服务端接收连接的队列大小
                    .option(ChannelOption.SO_BACKLOG, 128)
                    // 保持TCP连接(防止连接超时)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // 4. 绑定端口,启动服务(同步阻塞,直到服务启动完成)
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
            log.info("Netty RPC服务端启动成功,监听端口:{},注册中心地址:{}", port, registryAddress);

            // 5. 注册服务到注册中心(后续章节实现)
            registerService();

            // 6. 阻塞等待通道关闭(服务停止时才会继续执行)
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("Netty RPC服务端启动过程被中断,异常信息:{}", e.getMessage(), e);
        } finally {
            // 7. 优雅关闭线程组
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
            log.info("Netty RPC服务端已关闭");
        }
    }

    /**
     * 注册服务到注册中心(后续章节实现服务注册逻辑)
     */
    private void registerService() {
        try {
            // 获取本地IP地址(服务提供者的IP)
            String localIp = InetAddress.getLocalHost().getHostAddress();
            log.info("准备注册服务到注册中心,服务地址:{}:{}", localIp, port);
            // TODO:后续章节实现ZooKeeper注册逻辑
        } catch (UnknownHostException e) {
            log.error("获取本地IP地址失败,无法注册服务,异常信息:{}", e.getMessage(), e);
            throw new RuntimeException("获取本地IP地址失败:" + e.getMessage());
        }
    }
}
4.2.3 Netty 客户端实现(服务消费者)

服务消费者需要通过 Netty 客户端发送 RPC 请求,并接收响应:

复制代码
package com.ken.rpc.transport.netty;

import com.ken.rpc.entity.RpcRequest;
import com.ken.rpc.entity.RpcResponse;
import com.ken.rpc.serialize.FastJson2Serializer;
import com.ken.rpc.serialize.Serializer;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.net.InetSocketAddress;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Netty RPC客户端
 * 负责与服务提供者建立连接,发送RPC请求,接收响应
 *
 * @author ken
 */
@Slf4j
public class NettyRpcClient {

    /**
     * 服务端地址(IP:端口)
     */
    private final InetSocketAddress serverAddress;

    /**
     * 序列化工具(默认使用FastJSON2)
     */
    private final Serializer serializer;

    /**
     * 存储未处理的RPC请求(key:requestId,value:CompletableFuture<RpcResponse>)
     * 用于将响应与请求匹配(Netty是异步通信,需要通过requestId关联)
     */
    private final ConcurrentMap<String, CompletableFuture<RpcResponse>> unprocessedRequests;

    /**
     * Netty客户端通道(与服务端的连接通道)
     */
    private Channel channel;

    /**
     * Netty事件循环组(负责处理IO事件)
     */
    private final EventLoopGroup eventLoopGroup;

    public NettyRpcClient(InetSocketAddress serverAddress) {
        this(serverAddress, new FastJson2Serializer());
    }

    public NettyRpcClient(InetSocketAddress serverAddress, Serializer serializer) {
        this.serverAddress = serverAddress;
        this.serializer = serializer;
        this.unprocessedRequests = new ConcurrentHashMap<>();
        this.eventLoopGroup = new NioEventLoopGroup();
        // 初始化客户端连接
        this.initialize();
    }

    /**
     * 初始化Netty客户端,与服务端建立连接
     */
    private void initialize() {
        // 1. 校验服务端地址
        if (ObjectUtils.isEmpty(serverAddress) || StringUtils.isEmpty(serverAddress.getHostName())) {
            log.error("服务端地址非法,无法建立连接");
            throw new IllegalArgumentException("服务端地址非法");
        }

        try {
            // 2. 配置客户端启动参数
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    // 客户端通道类型(NIO)
                    .channel(NioSocketChannel.class)
                    // 连接超时时间(3秒)
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000)
                    // 保持TCP连接
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    // 禁用Nagle算法(减少延迟,适合RPC小数据传输)
                    .option(ChannelOption.TCP_NODELAY, true)
                    // 配置通道初始化器
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();

                            // ① 粘包/拆包处理器(与服务端对应)
                            pipeline.addLast(new LengthFieldBasedFrameDecoder(
                                    Integer.MAX_VALUE, 0, 4, 0, 4));
                            pipeline.addLast(new LengthFieldPrepender(4));

                            // ② 序列化/反序列化处理器(与服务端对应)
                            pipeline.addLast(new NettySerializerHandler(serializer, RpcRequest.class, RpcResponse.class));

                            // ③ 客户端响应处理器(接收服务端的响应,完成CompletableFuture)
                            pipeline.addLast(new NettyRpcClientHandler(unprocessedRequests));
                        }
                    });

            // 3. 与服务端建立连接(同步阻塞,直到连接建立完成)
            ChannelFuture channelFuture = bootstrap.connect(serverAddress).sync();
            log.info("Netty RPC客户端与服务端建立连接成功,服务端地址:{}:{}",
                    serverAddress.getHostName(), serverAddress.getPort());

            // 4. 保存通道引用,用于后续发送请求
            channel = channelFuture.channel();

            // 5. 监听通道关闭事件(连接断开时打印日志)
            channel.closeFuture().addListener(future -> {
                log.info("Netty RPC客户端与服务端连接断开,服务端地址:{}:{}",
                        serverAddress.getHostName(), serverAddress.getPort());
                eventLoopGroup.shutdownGracefully();
            });
        } catch (InterruptedException e) {
            log.error("Netty RPC客户端建立连接过程被中断,服务端地址:{}:{},异常信息:{}",
                    serverAddress.getHostName(), serverAddress.getPort(), e.getMessage(), e);
            eventLoopGroup.shutdownGracefully();
        }
    }

    /**
     * 发送RPC请求,返回CompletableFuture(异步获取响应)
     *
     * @param rpcRequest RPC请求实体
     * @return 包含RPC响应的CompletableFuture
     */
    public CompletableFuture<RpcResponse> sendRequest(RpcRequest rpcRequest) {
        // 1. 校验请求和通道
        if (ObjectUtils.isEmpty(rpcRequest) || StringUtils.isEmpty(rpcRequest.getRequestId())) {
            log.error("RPC请求非法(请求ID为空),无法发送请求");
            throw new IllegalArgumentException("RPC请求ID不能为空");
        }
        if (ObjectUtils.isEmpty(channel) || !channel.isActive()) {
            log.error("客户端通道未建立或已关闭,无法发送请求,服务端地址:{}:{}",
                    serverAddress.getHostName(), serverAddress.getPort());
            throw new RuntimeException("客户端通道不可用,无法发送请求");
        }

        // 2. 创建CompletableFuture,用于接收响应
        CompletableFuture<RpcResponse> resultFuture = new CompletableFuture<>();
        // 3. 将请求ID与CompletableFuture关联,存入未处理请求Map
        unprocessedRequests.put(rpcRequest.getRequestId(), resultFuture);

        // 4. 发送请求(Netty的writeAndFlush是异步操作,不会阻塞)
        channel.writeAndFlush(rpcRequest).addListener((ChannelFutureListener) future -> {
            if (future.isSuccess()) {
                log.info("RPC请求发送成功,请求ID:{},服务名称:{},方法名称:{}",
                        rpcRequest.getRequestId(), rpcRequest.getServiceName(), rpcRequest.getMethodName());
            } else {
                // 发送失败:移除未处理请求,完成CompletableFuture(异常)
                unprocessedRequests.remove(rpcRequest.getRequestId());
                resultFuture.completeExceptionally(future.cause());
                log.error("RPC请求发送失败,请求ID:{},异常信息:{}",
                        rpcRequest.getRequestId(), future.cause().getMessage(), future.cause());
            }
        });

        return resultFuture;
    }

    /**
     * 关闭客户端(优雅关闭线程组和通道)
     */
    public void close() {
        if (!ObjectUtils.isEmpty(channel) && channel.isActive()) {
            channel.close();
        }
        eventLoopGroup.shutdownGracefully();
        log.info("Netty RPC客户端已关闭,服务端地址:{}:{}",
                serverAddress.getHostName(), serverAddress.getPort());
    }
}
4.2.4 序列化 / 反序列化处理器

Netty 的ChannelHandler负责在通道中处理数据,我们需要自定义处理器,将 Netty 的ByteBuf与 RPC 的RpcRequest/RpcResponse相互转换:

复制代码
package com.ken.rpc.transport.netty;

import com.ken.rpc.entity.RpcRequest;
import com.ken.rpc.entity.RpcResponse;
import com.ken.rpc.serialize.Serializer;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.ReplayingDecoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;

import java.util.List;

/**
 * Netty序列化/反序列化处理器
 * 包含编码器(Request→ByteBuf)和解码器(ByteBuf→Response)
 *
 * @author ken
 */
@Slf4j
public class NettySerializerHandler {

    /**
     * RPC请求编码器:将RpcRequest对象转为ByteBuf(发送给服务端)
     */
    public static class RequestEncoder extends MessageToByteEncoder<RpcRequest> {
        private final Serializer serializer;

        public RequestEncoder(Serializer serializer) {
            this.serializer = serializer;
        }

        @Override
        protected void encode(ChannelHandlerContext ctx, RpcRequest msg, ByteBuf out) throws Exception {
            if (ObjectUtils.isEmpty(msg)) {
                log.error("RPC请求为空,无法编码");
                throw new IllegalArgumentException("RPC请求不能为空");
            }
            // 1. 序列化RpcRequest为字节数组
            byte[] bytes = serializer.serialize(msg);
            // 2. 将字节数组写入ByteBuf(Netty会自动处理后续传输)
            out.writeBytes(bytes);
            log.debug("RPC请求编码完成,请求ID:{},字节长度:{}", msg.getRequestId(), bytes.length);
        }
    }

    /**
     * RPC响应解码器:将ByteBuf转为RpcResponse对象(接收服务端响应)
     * 使用ReplayingDecoder简化解码逻辑(无需手动处理半包问题)
     */
    public static class ResponseDecoder extends ReplayingDecoder<Void> {
        private final Serializer serializer;

        public ResponseDecoder(Serializer serializer) {
            this.serializer = serializer;
        }

        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
            if (ObjectUtils.isEmpty(in)) {
                log.error("RPC响应ByteBuf为空,无法解码");
                return;
            }
            // 1. 读取ByteBuf中的所有字节
            int length = in.readableBytes();
            byte[] bytes = new byte[length];
            in.readBytes(bytes);
            // 2. 反序列化为RpcResponse对象
            RpcResponse rpcResponse = serializer.deserialize(bytes, RpcResponse.class);
            // 3. 将解码后的对象加入输出列表(交给下一个Handler处理)
            out.add(rpcResponse);
            log.debug("RPC响应解码完成,请求ID:{},响应是否成功:{}",
                    rpcResponse.getRequestId(), rpcResponse.isSuccess());
        }
    }

    /**
     * 通用构造方法:创建编码器和解码器
     *
     * @param serializer 序列化工具
     * @param requestCls 请求类(RpcRequest)
     * @param responseCls 响应类(RpcResponse)
     */
    public NettySerializerHandler(Serializer serializer, Class<?> requestCls, Class<?> responseCls) {
        // 此处仅为统一初始化,实际编码器和解码器已在Server和Client的ChannelPipeline中添加
        if (!RpcRequest.class.equals(requestCls) || !RpcResponse.class.equals(responseCls)) {
            log.warn("不支持的请求/响应类型,建议使用默认的RpcRequest和RpcResponse");
        }
    }
}

4.3 服务注册中心:基于 ZooKeeper 实现服务发现

设计思路:服务注册中心是 RPC 框架的 "导航地图",服务提供者启动时将 "服务名称→服务地址" 注册到 ZooKeeper,服务消费者通过服务名称从 ZooKeeper 获取所有可用的服务地址。我们采用 ZooKeeper 的持久节点存储服务根路径,临时节点存储具体的服务地址(服务下线时临时节点自动删除,实现服务健康检测)。

4.3.1 ZooKeeper 节点设计

定义 ZooKeeper 的节点结构,确保服务注册和发现的逻辑清晰:

  • 根节点/ken-rpc(持久节点,框架的根路径)
  • 服务节点/ken-rpc/com.ken.rpc.service.UserService(持久节点,每个服务对应一个节点,名称为服务接口全限定名)
  • 服务地址节点/ken-rpc/com.ken.rpc.service.UserService/192.168.1.100:8080(临时节点,存储服务提供者的 IP 和端口,服务下线时自动删除)
4.3.2 ZooKeeper 工具类实现

封装 ZooKeeper 的连接、节点创建、节点监听等操作,提供统一的服务注册和发现接口:

复制代码
package com.ken.rpc.registry.zookeeper;

import com.ken.rpc.exception.RegistryException;
import com.ken.rpc.registry.ServiceRegistry;
import com.ken.rpc.util.NetUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * 基于ZooKeeper的服务注册与发现实现
 *
 * @author ken
 */
@Slf4j
public class ZookeeperServiceRegistry implements ServiceRegistry, Watcher {

    /**
     * ZooKeeper会话超时时间(默认30秒)
     */
    private static final int SESSION_TIMEOUT = 30000;

    /**
     * RPC框架在ZooKeeper中的根节点路径
     */
    private static final String RPC_ROOT_PATH = "/ken-rpc";

    /**
     * ZooKeeper客户端实例
     */
    private final ZooKeeper zookeeper;

    /**
     * 用于等待ZooKeeper连接建立的计数器
     */
    private final CountDownLatch connectLatch;

    /**
     * 服务注册中心地址(如127.0.0.1:2181)
     */
    private final String registryAddress;

    /**
     * 初始化ZooKeeper客户端并建立连接
     *
     * @param registryAddress 服务注册中心地址(格式:IP:端口,多个地址用逗号分隔)
     * @throws RegistryException 注册中心连接失败时抛出
     */
    public ZookeeperServiceRegistry(String registryAddress) throws RegistryException {
        if (!StringUtils.hasText(registryAddress)) {
            log.error("ZooKeeper注册中心地址为空,无法初始化客户端");
            throw new RegistryException("ZooKeeper注册中心地址不能为空");
        }

        this.registryAddress = registryAddress;
        this.connectLatch = new CountDownLatch(1);

        try {
            // 初始化ZooKeeper客户端(this为Watcher,监听连接状态)
            this.zookeeper = new ZooKeeper(registryAddress, SESSION_TIMEOUT, this);
            // 等待连接建立完成(最多等待10秒)
            boolean connected = connectLatch.await(10, TimeUnit.SECONDS);
            if (!connected) {
                log.error("ZooKeeper连接超时,地址:{},超时时间:10秒", registryAddress);
                throw new RegistryException("ZooKeeper连接超时:" + registryAddress);
            }
            log.info("ZooKeeper客户端初始化成功,注册中心地址:{}", registryAddress);

            // 检查并创建根节点(/ken-rpc)
            createRootNode();
        } catch (IOException e) {
            log.error("ZooKeeper客户端初始化失败(IO异常),地址:{},异常信息:{}", registryAddress, e.getMessage(), e);
            throw new RegistryException("ZooKeeper客户端初始化失败:" + e.getMessage());
        } catch (InterruptedException e) {
            log.error("ZooKeeper连接等待被中断,地址:{},异常信息:{}", registryAddress, e.getMessage(), e);
            Thread.currentThread().interrupt();
            throw new RegistryException("ZooKeeper连接等待被中断:" + e.getMessage());
        }
    }

    /**
     * 创建RPC根节点(/ken-rpc),持久节点
     */
    private void createRootNode() {
        try {
            Stat stat = zookeeper.exists(RPC_ROOT_PATH, false);
            if (stat == null) {
                // 创建根节点:持久节点,开放所有权限
                zookeeper.create(RPC_ROOT_PATH, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                log.info("ZooKeeper RPC根节点创建成功,路径:{}", RPC_ROOT_PATH);
            } else {
                log.info("ZooKeeper RPC根节点已存在,路径:{}", RPC_ROOT_PATH);
            }
        } catch (KeeperException | InterruptedException e) {
            log.error("ZooKeeper创建根节点失败,路径:{},异常信息:{}", RPC_ROOT_PATH, e.getMessage(), e);
            throw new RegistryException("创建ZooKeeper根节点失败:" + e.getMessage());
        }
    }

    /**
     * 服务注册:将服务名称与服务地址注册到ZooKeeper
     *
     * @param serviceName 服务接口全限定名(如com.ken.rpc.service.UserService)
     * @param serviceAddress 服务地址(格式:IP:端口,如192.168.1.100:8080)
     * @throws RegistryException 服务注册失败时抛出
     */
    @Override
    public void register(String serviceName, String serviceAddress) throws RegistryException {
        // 1. 校验参数
        if (!StringUtils.hasText(serviceName)) {
            log.error("服务名称为空,无法注册服务");
            throw new RegistryException("服务名称不能为空");
        }
        if (!StringUtils.hasText(serviceAddress)) {
            log.error("服务地址为空,无法注册服务,服务名称:{}", serviceName);
            throw new RegistryException("服务地址不能为空");
        }
        if (!NetUtils.isValidAddress(serviceAddress)) {
            log.error("服务地址格式非法,无法注册服务,服务名称:{},地址:{}", serviceName, serviceAddress);
            throw new RegistryException("服务地址格式非法:" + serviceAddress);
        }

        try {
            // 2. 创建服务节点(/ken-rpc/服务名称),持久节点
            String servicePath = RPC_ROOT_PATH + "/" + serviceName;
            Stat serviceStat = zookeeper.exists(servicePath, false);
            if (serviceStat == null) {
                zookeeper.create(servicePath, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                log.info("ZooKeeper服务节点创建成功,路径:{}", servicePath);
            } else {
                log.info("ZooKeeper服务节点已存在,路径:{}", servicePath);
            }

            // 3. 创建服务地址节点(/ken-rpc/服务名称/IP:端口),临时节点(服务下线时自动删除)
            String addressPath = servicePath + "/" + serviceAddress;
            Stat addressStat = zookeeper.exists(addressPath, false);
            if (addressStat == null) {
                zookeeper.create(addressPath, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
                log.info("ZooKeeper服务地址节点创建成功,路径:{},服务名称:{}", addressPath, serviceName);
            } else {
                log.info("ZooKeeper服务地址节点已存在,路径:{},服务名称:{}", addressPath, serviceName);
            }
        } catch (KeeperException | InterruptedException e) {
            log.error("服务注册失败,服务名称:{},地址:{},异常信息:{}", serviceName, serviceAddress, e.getMessage(), e);
            throw new RegistryException("服务注册失败:" + e.getMessage());
        }
    }

    /**
     * 服务发现:根据服务名称从ZooKeeper获取所有可用的服务地址
     *
     * @param serviceName 服务接口全限定名
     * @return 服务地址列表(格式:IP:端口),无可用服务时返回空列表
     * @throws RegistryException 服务发现失败时抛出
     */
    @Override
    public List<String> discover(String serviceName) throws RegistryException {
        // 1. 校验参数
        if (!StringUtils.hasText(serviceName)) {
            log.error("服务名称为空,无法发现服务");
            throw new RegistryException("服务名称不能为空");
        }

        try {
            // 2. 拼接服务节点路径
            String servicePath = RPC_ROOT_PATH + "/" + serviceName;
            Stat serviceStat = zookeeper.exists(servicePath, false);
            if (serviceStat == null) {
                log.warn("ZooKeeper中不存在该服务节点,服务名称:{},路径:{}", serviceName, servicePath);
                return new ArrayList<>();
            }

            // 3. 获取服务节点下的所有地址节点(临时节点),并监听节点变化
            List<String> addressList = zookeeper.getChildren(servicePath, this);
            if (CollectionUtils.isEmpty(addressList)) {
                log.warn("服务无可用地址,服务名称:{},路径:{}", serviceName, servicePath);
                return new ArrayList<>();
            }

            // 4. 整理服务地址列表(地址节点名称即为服务地址)
            List<String> serviceAddressList = new ArrayList<>(addressList.size());
            for (String addressNode : addressList) {
                serviceAddressList.add(addressNode);
            }
            log.info("服务发现成功,服务名称:{},可用地址数量:{},地址列表:{}",
                    serviceName, serviceAddressList.size(), serviceAddressList);
            return serviceAddressList;
        } catch (KeeperException | InterruptedException e) {
            log.error("服务发现失败,服务名称:{},异常信息:{}", serviceName, e.getMessage(), e);
            throw new RegistryException("服务发现失败:" + e.getMessage());
        }
    }

    /**
     * ZooKeeper事件监听回调(处理连接状态和节点变化)
     *
     * @param event ZooKeeper事件
     */
    @Override
    public void process(WatchedEvent event) {
        Event.KeeperState state = event.getState();
        Event.EventType type = event.getType();
        String path = event.getPath();

        log.debug("ZooKeeper事件触发,状态:{},类型:{},路径:{}", state, type, path);

        // 处理连接状态事件
        if (state == Event.KeeperState.SyncConnected) {
            // 连接建立成功,倒计时器减1(释放等待线程)
            if (type == Event.EventType.None) {
                connectLatch.countDown();
                log.info("ZooKeeper连接建立成功,状态:{}", state);
            }
        } else if (state == Event.KeeperState.Disconnected) {
            log.warn("ZooKeeper连接已断开,状态:{}", state);
        } else if (state == Event.KeeperState.Expired) {
            log.error("ZooKeeper会话已过期,需要重新连接,状态:{}", state);
            // TODO:会话过期后重新初始化客户端(可选,增强可用性)
        }

        // 处理节点变化事件(服务地址新增/删除)
        if (type == Event.EventType.NodeChildrenChanged && path != null && path.startsWith(RPC_ROOT_PATH)) {
            log.info("服务地址节点发生变化,路径:{},重新发现服务", path);
            // 提取服务名称(路径格式:/ken-rpc/服务名称)
            String serviceName = path.substring(RPC_ROOT_PATH.length() + 1);
            if (StringUtils.hasText(serviceName)) {
                // 重新发现服务(更新本地服务地址缓存)
                discover(serviceName);
            }
        }
    }

    /**
     * 关闭ZooKeeper客户端连接
     */
    public void close() {
        if (zookeeper != null) {
            try {
                zookeeper.close();
                log.info("ZooKeeper客户端已关闭,注册中心地址:{}", registryAddress);
            } catch (InterruptedException e) {
                log.error("ZooKeeper客户端关闭失败,注册中心地址:{},异常信息:{}", registryAddress, e.getMessage(), e);
                Thread.currentThread().interrupt();
            }
        }
    }
}
4.3.3 服务注册异常定义
复制代码
package com.ken.rpc.exception;

/**
 * 服务注册/发现异常
 * 当服务与注册中心交互(注册、发现、连接)过程中出现错误时抛出
 *
 * @author ken
 */
public class RegistryException extends RuntimeException {

    public RegistryException(String message) {
        super(message);
    }

    public RegistryException(String message, Throwable cause) {
        super(message, cause);
    }
}

4.4 动态代理层:让远程调用像本地调用一样简单

设计思路:服务消费者不能直接调用远程方法,需要通过 "代理对象" 间接调用。我们使用 JDK 动态代理(基于接口)生成代理对象,当消费者调用代理对象的方法时,代理对象会自动封装 RPC 请求、发送网络请求、接收响应并返回结果,从而实现 "本地调用" 的体验。

4.4.1 动态代理工厂实现
复制代码
package com.ken.rpc.proxy;

import com.ken.rpc.entity.RpcRequest;
import com.ken.rpc.entity.RpcResponse;
import com.ken.rpc.exception.RpcException;
import com.ken.rpc.loadbalance.LoadBalancer;
import com.ken.rpc.loadbalance.impl.RoundRobinLoadBalancer;
import com.ken.rpc.registry.ServiceRegistry;
import com.ken.rpc.registry.zookeeper.ZookeeperServiceRegistry;
import com.ken.rpc.transport.ClientTransport;
import com.ken.rpc.transport.netty.NettyRpcClient;
import com.ken.rpc.util.IdUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
 * RPC动态代理工厂
 * 为服务消费者生成代理对象,将本地方法调用转为远程RPC调用
 *
 * @author ken
 */
@Slf4j
public class RpcProxyFactory implements InvocationHandler {

    /**
     * 服务接口Class对象
     */
    private final Class<?> serviceClass;

    /**
     * 服务版本号(默认1.0.0)
     */
    private final String serviceVersion;

    /**
     * 服务注册中心(用于发现服务地址)
     */
    private final ServiceRegistry serviceRegistry;

    /**
     * 负载均衡器(默认轮询算法)
     */
    private final LoadBalancer loadBalancer;

    /**
     * 客户端传输层(用于发送RPC请求)
     */
    private ClientTransport clientTransport;

    /**
     * 构造方法(默认使用ZooKeeper注册中心和轮询负载均衡)
     *
     * @param serviceClass 服务接口Class对象
     * @param serviceVersion 服务版本号
     * @param registryAddress 注册中心地址
     */
    public RpcProxyFactory(Class<?> serviceClass, String serviceVersion, String registryAddress) {
        this(serviceClass, serviceVersion,
                new ZookeeperServiceRegistry(registryAddress),
                new RoundRobinLoadBalancer());
    }

    public RpcProxyFactory(Class<?> serviceClass, String serviceVersion,
                           ServiceRegistry serviceRegistry, LoadBalancer loadBalancer) {
        // 1. 校验参数
        if (ObjectUtils.isEmpty(serviceClass) || !serviceClass.isInterface()) {
            log.error("服务类非法,必须是接口类型,类名:{}", serviceClass);
            throw new IllegalArgumentException("服务类必须是接口类型:" + serviceClass);
        }
        this.serviceClass = serviceClass;
        // 2. 服务版本号默认1.0.0
        this.serviceVersion = StringUtils.hasText(serviceVersion) ? serviceVersion : "1.0.0";
        // 3. 校验注册中心和负载均衡器
        this.serviceRegistry = ObjectUtils.isEmpty(serviceRegistry) ? new ZookeeperServiceRegistry("127.0.0.1:2181") : serviceRegistry;
        this.loadBalancer = ObjectUtils.isEmpty(loadBalancer) ? new RoundRobinLoadBalancer() : loadBalancer;
    }

    /**
     * 创建代理对象
     *
     * @return 服务接口的代理对象
     */
    @SuppressWarnings("unchecked")
    public <T> T getProxy() {
        // 使用JDK动态代理生成代理对象
        return (T) Proxy.newProxyInstance(
                serviceClass.getClassLoader(),
                new Class[]{serviceClass},
                this);
    }

    /**
     * 代理对象方法调用回调(核心逻辑)
     * 当消费者调用代理对象的方法时,会执行此方法
     *
     * @param proxy 代理对象
     * @param method 被调用的方法
     * @param args 方法参数
     * @return 远程方法执行结果
     * @throws Throwable 调用过程中出现的异常
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("开始执行RPC代理调用,服务接口:{},方法名称:{},参数数量:{}",
                serviceClass.getName(), method.getName(), args == null ? 0 : args.length);

        try {
            // 1. 从注册中心发现服务地址
            String serviceName = serviceClass.getName();
            List<String> serviceAddressList = serviceRegistry.discover(serviceName);
            if (ObjectUtils.isEmpty(serviceAddressList)) {
                log.error("服务无可用地址,无法执行RPC调用,服务名称:{}", serviceName);
                throw new RpcException("服务无可用地址:" + serviceName);
            }

            // 2. 负载均衡:选择一个服务地址
            String selectedAddress = loadBalancer.select(serviceAddressList, serviceName);
            log.info("负载均衡选择服务地址,服务名称:{},选中地址:{}", serviceName, selectedAddress);

            // 3. 解析服务地址为IP和端口
            String[] addressParts = selectedAddress.split(":");
            if (addressParts.length != 2) {
                log.error("服务地址格式非法,无法解析,地址:{}", selectedAddress);
                throw new RpcException("服务地址格式非法:" + selectedAddress);
            }
            String serverIp = addressParts[0];
            int serverPort = Integer.parseInt(addressParts[1]);
            InetSocketAddress serverAddress = new InetSocketAddress(serverIp, serverPort);

            // 4. 创建客户端传输层(Netty客户端)
            if (ObjectUtils.isEmpty(clientTransport) || !clientTransport.isConnected()) {
                clientTransport = new NettyRpcClient(serverAddress);
                log.info("创建Netty客户端连接,服务地址:{}:{}", serverIp, serverPort);
            }

            // 5. 构建RPC请求对象
            RpcRequest rpcRequest = RpcRequest.builder()
                    .requestId(IdUtils.generateRequestId()) // 生成唯一请求ID
                    .serviceName(serviceName) // 服务接口名称
                    .methodName(method.getName()) // 方法名称
                    .parameterTypes(method.getParameterTypes()) // 方法参数类型
                    .parameters(args) // 方法参数值
                    .serviceVersion(serviceVersion) // 服务版本号
                    .build();

            // 6. 发送RPC请求,获取响应(异步转同步,等待响应结果)
            CompletableFuture<RpcResponse> responseFuture = clientTransport.sendRequest(rpcRequest);
            RpcResponse rpcResponse = responseFuture.get(); // 阻塞等待响应

            // 7. 处理响应结果
            if (ObjectUtils.isEmpty(rpcResponse)) {
                log.error("RPC响应为空,请求ID:{}", rpcRequest.getRequestId());
                throw new RpcException("RPC响应为空,请求ID:" + rpcRequest.getRequestId());
            }
            if (!rpcRequest.getRequestId().equals(rpcResponse.getRequestId())) {
                log.error("RPC请求与响应ID不匹配,请求ID:{},响应ID:{}",
                        rpcRequest.getRequestId(), rpcResponse.getRequestId());
                throw new RpcException("请求与响应ID不匹配:" + rpcRequest.getRequestId() + " vs " + rpcResponse.getRequestId());
            }

            // 8. 判断调用是否成功,返回结果或抛出异常
            if (rpcResponse.isSuccess()) {
                log.info("RPC调用成功,请求ID:{},服务接口:{},方法名称:{}",
                        rpcRequest.getRequestId(), serviceName, method.getName());
                return rpcResponse.getResult();
            } else {
                log.error("RPC调用失败,请求ID:{},异常信息:{}",
                        rpcRequest.getRequestId(), rpcResponse.getException().getMessage());
                throw new RpcException("RPC调用失败:" + rpcResponse.getException().getMessage(), rpcResponse.getException());
            }
        } catch (Exception e) {
            log.error("RPC代理调用异常,服务接口:{},方法名称:{},异常信息:{}",
                    serviceClass.getName(), method.getName(), e.getMessage(), e);
            throw new RpcException("RPC调用异常:" + e.getMessage(), e);
        }
    }

    /**
     * 关闭客户端传输层连接
     */
    public void close() {
        if (!ObjectUtils.isEmpty(clientTransport)) {
            clientTransport.close();
            log.info("RPC代理客户端连接已关闭,服务接口:{}", serviceClass.getName());
        }
    }
}
4.4.2 工具类:生成唯一请求 ID

为了确保 RPC 请求与响应的对应(Netty 异步通信),需要生成唯一的requestId,这里使用 UUID 简化实现:

复制代码
package com.ken.rpc.util;

import java.util.UUID;

/**
 * ID生成工具类
 * 生成RPC请求ID、服务ID等唯一标识
 *
 * @author ken
 */
public class IdUtils {

    /**
     * 生成RPC请求ID(基于UUID,去除横杠)
     *
     * @return 唯一请求ID(32位字符串)
     */
    public static String generateRequestId() {
        // UUID格式:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,去除横杠后为32位
        return UUID.randomUUID().toString().replace("-", "");
    }
}

4.5 负载均衡层:实现服务调用的负载分发

设计思路:当一个服务有多个提供者(如 UserService 部署在 3 台服务器上)时,消费者需要选择其中一台调用,这就是负载均衡的作用。我们定义负载均衡接口,实现常见的负载均衡算法(轮询、随机、加权随机),让框架支持灵活切换。

4.5.1 负载均衡接口定义
复制代码
package com.ken.rpc.loadbalance;

import com.ken.rpc.exception.LoadBalanceException;
import org.springframework.util.CollectionUtils;

import java.util.List;

/**
 * 负载均衡接口
 * 定义从服务地址列表中选择一个地址的方法
 *
 * @author ken
 */
public interface LoadBalancer {

    /**
     * 从服务地址列表中选择一个地址
     *
     * @param serviceAddressList 服务地址列表(格式:IP:端口)
     * @param serviceName 服务名称(用于区分不同服务的负载均衡状态)
     * @return 选中的服务地址
     * @throws LoadBalanceException 负载均衡失败时抛出
     */
    String select(List<String> serviceAddressList, String serviceName) throws LoadBalanceException;

    /**
     * 校验服务地址列表(通用方法,所有实现类可复用)
     *
     * @param serviceAddressList 服务地址列表
     * @param serviceName 服务名称
     * @throws LoadBalanceException 地址列表为空时抛出
     */
    default void validateAddressList(List<String> serviceAddressList, String serviceName) throws LoadBalanceException {
        if (CollectionUtils.isEmpty(serviceAddressList)) {
            throw new LoadBalanceException("服务无可用地址,无法执行负载均衡,服务名称:" + serviceName);
        }
    }
}
4.5.2 轮询算法实现(最常用)

轮询算法:按顺序依次选择服务地址,如地址列表为 [A,B,C],则调用顺序为 A→B→C→A→B→C...

复制代码
package com.ken.rpc.loadbalance.impl;

import com.ken.rpc.exception.LoadBalanceException;
import com.ken.rpc.loadbalance.LoadBalancer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 轮询负载均衡算法
 * 按顺序依次选择服务地址,适用于服务节点性能相近的场景
 *
 * @author ken
 */
@Slf4j
public class RoundRobinLoadBalancer implements LoadBalancer {

    /**
     * 每个服务的轮询计数器(key:服务名称,value:当前计数)
     * 使用AtomicInteger保证线程安全
     */
    private final ConcurrentMap<String, AtomicInteger> countMap = new ConcurrentHashMap<>();

    @Override
    public String select(List<String> serviceAddressList, String serviceName) throws LoadBalanceException {
        // 1. 校验地址列表
        validateAddressList(serviceAddressList, serviceName);
        int addressCount = serviceAddressList.size();
        if (addressCount == 1) {
            // 只有一个地址,直接返回
            String address = serviceAddressList.get(0);
            log.debug("轮询负载均衡:服务只有一个地址,直接返回,服务名称:{},地址:{}", serviceName, address);
            return address;
        }

        // 2. 获取当前服务的轮询计数器(不存在则初始化)
        AtomicInteger count = countMap.computeIfAbsent(serviceName, k -> new AtomicInteger(0));

        // 3. 原子递增计数,并取模(避免计数溢出)
        int currentCount = count.getAndIncrement();
        // 取模确保选择的索引在地址列表范围内
        int selectedIndex = currentCount % addressCount;

        // 4. 处理计数溢出(当计数超过Integer.MAX_VALUE时重置为0)
        if (currentCount > Integer.MAX_VALUE - 1000) {
            count.set(0);
            log.debug("轮询计数器即将溢出,重置为0,服务名称:{},当前计数:{}", serviceName, currentCount);
        }

        // 5. 返回选中的地址
        String selectedAddress = serviceAddressList.get(selectedIndex);
        log.debug("轮询负载均衡选择完成,服务名称:{},地址列表大小:{},当前计数:{},选中索引:{},选中地址:{}",
                serviceName, addressCount, currentCount, selectedIndex, selectedAddress);
        return selectedAddress;
    }
}
4.5.3 负载均衡异常定义
复制代码
package com.ken.rpc.exception;

/**
 * 负载均衡异常
 * 当从服务地址列表选择地址过程中出现错误时抛出
 *
 * @author ken
 */
public class LoadBalanceException extends RuntimeException {

    public LoadBalanceException(String message) {
        super(message);
    }

    public LoadBalanceException(String message, Throwable cause) {
        super(message, cause);
    }
}

4.6 服务提供者与消费者:注解驱动开发

设计思路 :为了简化框架使用,我们采用注解驱动开发:服务提供者用@RpcService注解标记服务实现类,框架自动扫描并注册服务;服务消费者用@RpcReference注解标记服务接口,框架自动生成代理对象,消费者直接注入使用。

4.6.1 服务提供者注解:@RpcService
复制代码
package com.ken.rpc.annotation;

import org.springframework.stereotype.Component;

import java.lang.annotation.*;

/**
 * RPC服务提供者注解
 * 标记服务实现类,框架自动扫描并注册到注册中心
 *
 * @author ken
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Component // 继承Spring的@Component,让Spring能扫描到该类
public @interface RpcService {

    /**
     * 服务接口Class对象(默认当前类实现的第一个接口)
     */
    Class<?> interfaceClass() default void.class;

    /**
     * 服务接口名称(与interfaceClass二选一,优先使用interfaceClass)
     */
    String interfaceName() default "";

    /**
     * 服务版本号(默认1.0.0)
     */
    String serviceVersion() default "1.0.0";
}
4.6.2 服务消费者注解:@RpcReference
复制代码
package com.ken.rpc.annotation;

import org.springframework.beans.factory.annotation.Autowired;

import java.lang.annotation.*;

/**
 * RPC服务消费者注解
 * 标记服务接口字段,框架自动生成代理对象并注入到Spring Bean中
 *
 * @author ken
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Autowired // 结合Spring自动注入机制,触发代理对象创建
public @interface RpcReference {

    /**
     * 服务接口Class对象(默认取字段的类型)
     */
    Class<?> interfaceClass() default void.class;

    /**
     * 服务版本号(默认1.0.0,需与服务提供者版本一致)
     */
    String serviceVersion() default "1.0.0";

    /**
     * 注册中心地址(默认从全局配置读取,可单独指定)
     */
    String registryAddress() default "";
}

4.6.3 服务扫描与注册实现(核心)

需要实现两个核心逻辑:服务提供者扫描 (识别@RpcService注解并注册服务)和服务消费者注入 (识别@RpcReference注解并注入代理对象)。我们借助 Spring 的BeanPostProcessor机制,在 Bean 初始化阶段完成这些操作。

4.6.3.1 服务映射器:存储服务接口与实现的对应关系

服务端接收 RPC 请求后,需要根据 "服务名称 + 版本号" 找到对应的实现类,因此需要一个线程安全的服务映射器:

复制代码
package com.ken.rpc.service;

import com.ken.rpc.exception.RpcException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * RPC服务映射器
 * 存储"服务接口+版本号"与服务实现类的对应关系,供服务端处理请求时查询
 *
 * @author ken
 */
@Slf4j
public class RpcServiceMapper {

    /**
     * 服务映射表:key=服务接口全限定名:版本号(如com.ken.rpc.service.UserService:1.0.0),value=服务实现类实例
     */
    private final Map<String, Object> serviceMap = new ConcurrentHashMap<>();

    /**
     * 注册服务到映射表
     *
     * @param serviceInterface 服务接口Class
     * @param serviceVersion 服务版本号
     * @param serviceImpl 服务实现类实例
     */
    public void registerService(Class<?> serviceInterface, String serviceVersion, Object serviceImpl) {
        // 1. 校验参数
        if (ObjectUtils.isEmpty(serviceInterface) || !serviceInterface.isInterface()) {
            log.error("服务接口非法,必须是接口类型,接口类:{}", serviceInterface);
            throw new RpcException("服务接口必须是接口类型:" + serviceInterface);
        }
        if (ObjectUtils.isEmpty(serviceImpl)) {
            log.error("服务实现类实例为空,接口类:{},版本号:{}", serviceInterface.getName(), serviceVersion);
            throw new RpcException("服务实现类实例不能为空");
        }
        if (!serviceInterface.isAssignableFrom(serviceImpl.getClass())) {
            log.error("服务实现类未实现指定接口,接口类:{},实现类:{}", serviceInterface.getName(), serviceImpl.getClass().getName());
            throw new RpcException("服务实现类未实现接口:" + serviceInterface.getName());
        }

        // 2. 构建服务key(接口名:版本号)
        String serviceKey = buildServiceKey(serviceInterface.getName(), serviceVersion);

        // 3. 注册服务(若已存在则覆盖,支持服务热更新)
        if (serviceMap.containsKey(serviceKey)) {
            log.warn("服务已存在,将覆盖旧服务,服务key:{},旧实现类:{},新实现类:{}",
                    serviceKey, serviceMap.get(serviceKey).getClass().getName(), serviceImpl.getClass().getName());
        }
        serviceMap.put(serviceKey, serviceImpl);
        log.info("服务注册到映射表成功,服务key:{},接口类:{},实现类:{},版本号:{}",
                serviceKey, serviceInterface.getName(), serviceImpl.getClass().getName(), serviceVersion);
    }

    /**
     * 根据服务接口名和版本号获取服务实现类
     *
     * @param serviceName 服务接口全限定名
     * @param serviceVersion 服务版本号
     * @return 服务实现类实例
     * @throws RpcException 服务不存在时抛出
     */
    public Object getService(String serviceName, String serviceVersion) {
        // 1. 校验参数
        if (ObjectUtils.isEmpty(serviceName)) {
            log.error("服务名称为空,无法获取服务实现");
            throw new RpcException("服务名称不能为空");
        }
        String serviceKey = buildServiceKey(serviceName, serviceVersion);

        // 2. 查询服务
        Object serviceImpl = serviceMap.get(serviceKey);
        if (ObjectUtils.isEmpty(serviceImpl)) {
            log.error("服务不存在,服务key:{},可用服务列表:{}", serviceKey, serviceMap.keySet());
            throw new RpcException("服务不存在:" + serviceKey);
        }

        log.debug("获取服务实现成功,服务key:{},实现类:{}", serviceKey, serviceImpl.getClass().getName());
        return serviceImpl;
    }

    /**
     * 构建服务key(接口名:版本号)
     * 版本号为空时,默认使用"1.0.0"
     *
     * @param serviceName 服务接口名
     * @param serviceVersion 服务版本号
     * @return 服务key
     */
    private String buildServiceKey(String serviceName, String serviceVersion) {
        String version = ObjectUtils.isEmpty(serviceVersion) ? "1.0.0" : serviceVersion;
        return serviceName + ":" + version;
    }

    /**
     * 获取所有已注册的服务key
     *
     * @return 服务key列表
     */
    public Map<String, Object> getServiceMap() {
        return serviceMap;
    }
}
4.6.3.2 服务提供者扫描处理器

实现BeanPostProcessor,在 Spring 初始化@RpcService标记的 Bean 后,将其注册到RpcServiceMapper和 ZooKeeper:

复制代码
package com.ken.rpc.processor;

import com.ken.rpc.annotation.RpcService;
import com.ken.rpc.registry.ServiceRegistry;
import com.ken.rpc.service.RpcServiceMapper;
import com.ken.rpc.transport.netty.NettyRpcServer;
import com.ken.rpc.util.NetUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.lang.reflect.Method;

/**
 * RPC服务提供者扫描处理器
 * 扫描带有@RpcService注解的Bean,完成服务注册(映射表+注册中心)并启动Netty服务端
 *
 * @author ken
 */
@Slf4j
@Component
public class RpcServiceBeanPostProcessor implements BeanPostProcessor {

    /**
     * 服务映射器(注入Spring容器)
     */
    private final RpcServiceMapper rpcServiceMapper;

    /**
     * 服务注册中心(注入Spring容器,默认ZooKeeper实现)
     */
    private final ServiceRegistry serviceRegistry;

    /**
     * Netty服务端端口(从配置文件读取,默认8080)
     */
    @Value("${rpc.server.port:8080}")
    private int serverPort;

    /**
     * 注册中心地址(从配置文件读取,默认127.0.0.1:2181)
     */
    @Value("${rpc.registry.address:127.0.0.1:2181}")
    private String registryAddress;

    /**
     * Netty服务端实例(用于启动和关闭)
     */
    private NettyRpcServer nettyRpcServer;

    public RpcServiceBeanPostProcessor(RpcServiceMapper rpcServiceMapper, ServiceRegistry serviceRegistry) {
        this.rpcServiceMapper = rpcServiceMapper;
        this.serviceRegistry = serviceRegistry;
    }

    /**
     * Bean初始化后执行:处理@RpcService注解的Bean
     *
     * @param bean Bean实例
     * @param beanName Bean名称
     * @return 原Bean实例(不修改Bean)
     * @throws BeansException Bean处理异常
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 1. 判断Bean是否带有@RpcService注解
        RpcService rpcService = bean.getClass().getAnnotation(RpcService.class);
        if (ObjectUtils.isEmpty(rpcService)) {
            return bean; // 非RPC服务Bean,直接返回
        }

        // 2. 解析@RpcService注解的属性
        Class<?> serviceInterface = rpcService.interfaceClass();
        String serviceInterfaceName = rpcService.interfaceName();
        String serviceVersion = rpcService.serviceVersion();

        // 3. 确定服务接口(优先使用interfaceClass,若未指定则取Bean实现的第一个接口)
        if (serviceInterface == void.class) {
            Class<?>[] interfaces = bean.getClass().getInterfaces();
            if (ObjectUtils.isEmpty(interfaces)) {
                log.error("服务实现类未实现任何接口,无法注册服务,Bean名称:{},实现类:{}",
                        beanName, bean.getClass().getName());
                throw new BeansException("服务实现类未实现接口:" + bean.getClass().getName()) {};
            }
            // 默认取第一个实现的接口
            serviceInterface = interfaces[0];
            log.warn("未指定服务接口(interfaceClass),默认使用实现类的第一个接口,Bean名称:{},接口类:{}",
                    beanName, serviceInterface.getName());
        }

        // 4. 确定服务接口名(优先使用interfaceName,若未指定则取interfaceClass的全限定名)
        if (!StringUtils.hasText(serviceInterfaceName)) {
            serviceInterfaceName = serviceInterface.getName();
        }

        // 5. 注册服务到RpcServiceMapper(本地映射)
        rpcServiceMapper.registerService(serviceInterface, serviceVersion, bean);

        // 6. 注册服务到注册中心(ZooKeeper)
        String serviceAddress = NetUtils.getLocalIp() + ":" + serverPort;
        try {
            serviceRegistry.register(serviceInterfaceName, serviceAddress);
            log.info("服务注册到注册中心成功,服务接口:{},版本号:{},服务地址:{},Bean名称:{}",
                    serviceInterfaceName, serviceVersion, serviceAddress, beanName);
        } catch (Exception e) {
            log.error("服务注册到注册中心失败,服务接口:{},版本号:{},服务地址:{},异常信息:{}",
                    serviceInterfaceName, serviceVersion, serviceAddress, e.getMessage(), e);
            throw new BeansException("服务注册失败:" + serviceInterfaceName) {};
        }

        return bean;
    }

    /**
     * 初始化方法:启动Netty服务端(在所有Bean初始化前执行)
     */
    @PostConstruct
    public void startNettyServer() {
        if (!ObjectUtils.isEmpty(nettyRpcServer)) {
            log.warn("Netty服务端已启动,无需重复启动,端口:{}", serverPort);
            return;
        }

        // 启动Netty服务端(单独线程启动,避免阻塞Spring上下文初始化)
        new Thread(() -> {
            nettyRpcServer = new NettyRpcServer(serverPort, registryAddress);
            nettyRpcServer.start(); // 启动服务端(阻塞直到服务关闭)
        }, "netty-rpc-server-thread-" + serverPort).start();

        log.info("Netty服务端启动线程已创建,端口:{},注册中心地址:{}", serverPort, registryAddress);
    }

    /**
     * 销毁方法:关闭Netty服务端和注册中心连接(Spring上下文关闭时执行)
     */
    @PreDestroy
    public void stopNettyServer() {
        // 1. 关闭Netty服务端
        if (!ObjectUtils.isEmpty(nettyRpcServer)) {
            nettyRpcServer.close(); // 自定义close方法,优雅关闭Netty线程组
            log.info("Netty服务端已关闭,端口:{}", serverPort);
        }

        // 2. 关闭注册中心连接(若ZooKeeper实现,需调用close方法)
        if (serviceRegistry instanceof AutoCloseable) {
            try {
                ((AutoCloseable) serviceRegistry).close();
                log.info("服务注册中心连接已关闭,地址:{}", registryAddress);
            } catch (Exception e) {
                log.error("服务注册中心连接关闭失败,地址:{},异常信息:{}",
                        registryAddress, e.getMessage(), e);
            }
        }
    }

    /**
     * 关闭Netty服务端(供外部调用)
     */
    public void close() {
        stopNettyServer();
    }
}
4.6.3.3 服务消费者注入处理器

实现BeanPostProcessor,在 Spring 初始化带有@RpcReference字段的 Bean 时,生成代理对象并注入字段:

复制代码
package com.ken.rpc.processor;

import com.ken.rpc.annotation.RpcReference;
import com.ken.rpc.proxy.RpcProxyFactory;
import com.ken.rpc.registry.ServiceRegistry;
import com.ken.rpc.registry.zookeeper.ZookeeperServiceRegistry;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * RPC服务消费者注入处理器
 * 扫描Bean中带有@RpcReference注解的字段,生成代理对象并注入
 *
 * @author ken
 */
@Slf4j
@Component
public class RpcReferenceBeanPostProcessor implements BeanPostProcessor {

    /**
     * 代理对象缓存:key=服务接口名:版本号,value=代理对象(避免重复生成)
     */
    private final ConcurrentMap<String, Object> proxyCache = new ConcurrentHashMap<>();

    /**
     * 全局注册中心地址(从配置文件读取,默认127.0.0.1:2181)
     */
    @Value("${rpc.registry.address:127.0.0.1:2181}")
    private String globalRegistryAddress;

    /**
     * Bean初始化后执行:处理@RpcReference注解的字段
     *
     * @param bean Bean实例
     * @param beanName Bean名称
     * @return 原Bean实例(已注入代理对象)
     * @throws BeansException Bean处理异常
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 1. 获取Bean的所有字段
        Field[] fields = bean.getClass().getDeclaredFields();
        if (ObjectUtils.isEmpty(fields)) {
            return bean; // 无字段,直接返回
        }

        // 2. 遍历字段,处理带有@RpcReference注解的字段
        for (Field field : fields) {
            RpcReference rpcReference = field.getAnnotation(RpcReference.class);
            if (ObjectUtils.isEmpty(rpcReference)) {
                continue; // 非RPC引用字段,跳过
            }

            // 3. 解析@RpcReference注解的属性
            Class<?> serviceInterface = rpcReference.interfaceClass();
            String serviceVersion = rpcReference.serviceVersion();
            String registryAddress = rpcReference.registryAddress();

            // 4. 确定服务接口(优先使用interfaceClass,若未指定则取字段类型)
            if (serviceInterface == void.class) {
                serviceInterface = field.getType();
                log.warn("未指定服务接口(interfaceClass),默认使用字段类型,Bean名称:{},字段名:{},接口类:{}",
                        beanName, field.getName(), serviceInterface.getName());
            }

            // 5. 确定注册中心地址(优先使用注解的registryAddress,若未指定则使用全局配置)
            if (!StringUtils.hasText(registryAddress)) {
                registryAddress = globalRegistryAddress;
            }

            // 6. 生成或获取代理对象(从缓存获取,避免重复生成)
            String proxyKey = buildProxyKey(serviceInterface.getName(), serviceVersion);
            Object proxy = proxyCache.computeIfAbsent(proxyKey, k -> {
                log.info("生成RPC代理对象,服务接口:{},版本号:{},注册中心地址:{}",
                        serviceInterface.getName(), serviceVersion, registryAddress);
                // 创建服务注册中心实例(ZooKeeper)
                ServiceRegistry serviceRegistry = new ZookeeperServiceRegistry(registryAddress);
                // 创建代理工厂并生成代理对象
                RpcProxyFactory proxyFactory = new RpcProxyFactory(serviceInterface, serviceVersion, serviceRegistry);
                return proxyFactory.getProxy();
            });

            // 7. 注入代理对象到字段(设置字段可访问,突破private修饰符)
            ReflectionUtils.makeAccessible(field);
            try {
                field.set(bean, proxy);
                log.info("RPC代理对象注入成功,Bean名称:{},字段名:{},服务接口:{},版本号:{}",
                        beanName, field.getName(), serviceInterface.getName(), serviceVersion);
            } catch (IllegalAccessException e) {
                log.error("RPC代理对象注入失败,Bean名称:{},字段名:{},服务接口:{},异常信息:{}",
                        beanName, field.getName(), serviceInterface.getName(), e.getMessage(), e);
                throw new BeansException("代理对象注入失败:" + field.getName()) {};
            }
        }

        return bean;
    }

    /**
     * 构建代理对象缓存key(服务接口名:版本号)
     *
     * @param serviceName 服务接口名
     * @param serviceVersion 服务版本号
     * @return 代理缓存key
     */
    private String buildProxyKey(String serviceName, String serviceVersion) {
        String version = ObjectUtils.isEmpty(serviceVersion) ? "1.0.0" : serviceVersion;
        return serviceName + ":" + version;
    }
}

4.6.4 框架自动配置类(简化用户配置)

为了让用户无需手动配置 Bean,我们使用 Spring Boot 的自动配置机制,通过@Configuration@ConditionalOnMissingBean自动注册核心 Bean:

复制代码
package com.ken.rpc.config;

import com.ken.rpc.processor.RpcReferenceBeanPostProcessor;
import com.ken.rpc.processor.RpcServiceBeanPostProcessor;
import com.ken.rpc.registry.ServiceRegistry;
import com.ken.rpc.registry.zookeeper.ZookeeperServiceRegistry;
import com.ken.rpc.service.RpcServiceMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * RPC框架自动配置类
 * 自动注册核心Bean,用户无需手动配置
 *
 * @author ken
 */
@Configuration
public class RpcAutoConfiguration {

    /**
     * 注册服务映射器(RpcServiceMapper)
     * 若用户未自定义,则使用默认实现
     */
    @Bean
    @ConditionalOnMissingBean
    public RpcServiceMapper rpcServiceMapper() {
        return new RpcServiceMapper();
    }

    /**
     * 注册服务注册中心(默认ZooKeeper实现)
     * 若用户未自定义,则使用ZookeeperServiceRegistry
     */
    @Bean
    @ConditionalOnMissingBean
    public ServiceRegistry serviceRegistry() {
        // 默认注册中心地址:127.0.0.1:2181(用户可通过配置文件覆盖)
        return new ZookeeperServiceRegistry("127.0.0.1:2181");
    }

    /**
     * 注册服务提供者扫描处理器
     */
    @Bean
    @ConditionalOnMissingBean
    public RpcServiceBeanPostProcessor rpcServiceBeanPostProcessor(RpcServiceMapper rpcServiceMapper, ServiceRegistry serviceRegistry) {
        return new RpcServiceBeanPostProcessor(rpcServiceMapper, serviceRegistry);
    }

    /**
     * 注册服务消费者注入处理器
     */
    @Bean
    @ConditionalOnMissingBean
    public RpcReferenceBeanPostProcessor rpcReferenceBeanPostProcessor() {
        return new RpcReferenceBeanPostProcessor();
    }
}

4.6.5 完整使用示例(可直接运行)

为了让用户直观理解框架使用流程,我们实现一个 "用户服务" 示例,包含服务接口、服务提供者、服务消费者三部分。

4.6.5.1 1. 定义服务接口(公共模块)

创建user-api模块,定义服务接口(服务提供者和消费者都需依赖此模块):

复制代码
package com.ken.rpc.service;

import com.ken.rpc.entity.User;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;

/**
 * 用户服务RPC接口
 *
 * @author ken
 */
@Tag(name = "UserService", description = "用户服务RPC接口,提供用户查询、创建等功能")
public interface UserService {

    /**
     * 根据用户ID查询用户信息
     *
     * @param userId 用户ID(非负整数)
     * @return 用户信息(userId不存在时返回null)
     */
    @Operation(summary = "查询用户", description = "根据用户ID查询用户详情,支持ID范围:1-10000")
    User getUserById(@Parameter(description = "用户ID", required = true, example = "1001") Long userId);

    /**
     * 创建新用户
     *
     * @param user 用户信息(username和age为必填字段)
     * @return 创建成功的用户ID(自增主键)
     */
    @Operation(summary = "创建用户", description = "创建新用户,返回自增用户ID")
    Long createUser(@Parameter(description = "用户信息", required = true) User user);
}

用户实体类(需实现Serializable,支持序列化):

复制代码
package com.ken.rpc.entity;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * 用户实体类
 *
 * @author ken
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "用户实体类,存储用户基本信息")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 用户ID(自增主键)
     */
    @Schema(description = "用户ID(自增主键)", example = "1001")
    private Long userId;

    /**
     * 用户名(非空,长度1-20)
     */
    @Schema(description = "用户名", required = true, example = "zhangsan")
    private String username;

    /**
     * 用户年龄(1-120)
     */
    @Schema(description = "用户年龄", required = true, example = "25")
    private Integer age;

    /**
     * 用户邮箱(可选,格式需符合邮箱规范)
     */
    @Schema(description = "用户邮箱", example = "zhangsan@example.com")
    private String email;
}
4.6.5.2 2. 服务提供者实现(user-provider 模块)

创建user-provider模块,依赖user-api,实现UserService并通过@RpcService暴露服务。

(1)服务实现类(集成 MyBatis-Plus 操作数据库)
复制代码
package com.ken.rpc.provider.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ken.rpc.annotation.RpcService;
import com.ken.rpc.entity.User;
import com.ken.rpc.mapper.UserMapper;
import com.ken.rpc.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;

/**
 * 用户服务实现类(RPC服务提供者)
 *
 * @author ken
 */
@Slf4j
@Service
@RpcService(interfaceClass = UserService.class, serviceVersion = "1.0.0")
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public User getUserById(Long userId) {
        // 1. 校验参数
        if (ObjectUtils.isEmpty(userId) || userId <= 0) {
            log.error("查询用户失败:用户ID非法,userId:{}", userId);
            throw new IllegalArgumentException("用户ID必须为正整数:" + userId);
        }

        // 2. MyBatis-Plus查询用户
        LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery(User.class)
                .eq(User::getUserId, userId);
        User user = userMapper.selectOne(queryWrapper);

        // 3. 日志记录
        if (ObjectUtils.isEmpty(user)) {
            log.warn("查询用户不存在,userId:{}", userId);
        } else {
            log.info("查询用户成功,userId:{},username:{}", userId, user.getUsername());
        }

        return user;
    }

    @Override
    public Long createUser(User user) {
        // 1. 校验参数
        if (ObjectUtils.isEmpty(user)) {
            log.error("创建用户失败:用户信息为空");
            throw new IllegalArgumentException("用户信息不能为空");
        }
        if (!StringUtils.hasText(user.getUsername()) || user.getUsername().length() > 20) {
            log.error("创建用户失败:用户名非法,username:{}", user.getUsername());
            throw new IllegalArgumentException("用户名必须为1-20位非空字符串:" + user.getUsername());
        }
        if (ObjectUtils.isEmpty(user.getAge()) || user.getAge() < 1 || user.getAge() > 120) {
            log.error("创建用户失败:年龄非法,age:{}", user.getAge());
            throw new IllegalArgumentException("年龄必须为1-120的整数:" + user.getAge());
        }

        // 2. MyBatis-Plus插入用户(自增主键会自动回写)
        int insertCount = userMapper.insert(user);
        if (insertCount != 1) {
            log.error("创建用户失败:插入数据库失败,影响行数:{},用户信息:{}", insertCount, user);
            throw new RuntimeException("创建用户失败:数据库插入异常");
        }

        // 3. 日志记录
        log.info("创建用户成功,userId:{},username:{}", user.getUserId(), user.getUsername());

        return user.getUserId();
    }
}
(2)MyBatis-Plus Mapper 接口
复制代码
package com.ken.rpc.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ken.rpc.entity.User;
import org.apache.ibatis.annotations.Mapper;

/**
 * 用户Mapper接口(MyBatis-Plus)
 *
 * @author ken
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
(3)数据库表结构(MySQL 8.0)
复制代码
-- 创建用户表
CREATE TABLE `user` (
  `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID(自增主键)',
  `username` varchar(20) NOT NULL COMMENT '用户名',
  `age` int NOT NULL COMMENT '用户年龄',
  `email` varchar(50) DEFAULT NULL COMMENT '用户邮箱',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`user_id`),
  UNIQUE KEY `uk_username` (`username`) COMMENT '用户名唯一索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

-- 插入测试数据
INSERT INTO `user` (`username`, `age`, `email`) VALUES 
('zhangsan', 25, 'zhangsan@example.com'),
('lisi', 30, 'lisi@example.com');
(4)服务提供者配置文件(application.yml)
复制代码
# Spring配置
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/rpc_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456

# MyBatis-Plus配置
mybatis-plus:
  mapper-locations: classpath:mapper/**/*.xml
  type-aliases-package: com.ken.rpc.entity
  configuration:
    map-underscore-to-camel-case: true # 下划线转驼峰
    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl # 日志实现

# RPC框架配置
rpc:
  server:
    port: 8080 # Netty服务端端口
  registry:
    address: 127.0.0.1:2181 # ZooKeeper注册中心地址

# Swagger3配置
springdoc:
  api-docs:
    path: /v3/api-docs
  swagger-ui:
    path: /swagger-ui.html
    operationsSorter: method
  packages-to-scan: com.ken.rpc.provider.controller
(5)服务提供者启动类
复制代码
package com.ken.rpc.provider;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;

/**
 * RPC服务提供者启动类(用户服务)
 *
 * @author ken
 */
@SpringBootApplication(scanBasePackages = "com.ken.rpc")
@MapperScan("com.ken.rpc.mapper") // 扫描MyBatis Mapper
@EntityScan("com.ken.rpc.entity") // 扫描实体类
@OpenAPIDefinition(info = @Info(title = "User Provider API", version = "1.0", description = "用户服务提供者接口文档"))
public class UserProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserProviderApplication.class, args);
    }
}
4.6.5.3 3. 服务消费者实现(user-consumer 模块)

创建user-consumer模块,依赖user-api,通过@RpcReference注入UserService代理对象,提供 HTTP 接口供测试。

(1)消费者控制器(集成 Swagger3)
复制代码
package com.ken.rpc.consumer.controller;

import com.ken.rpc.annotation.RpcReference;
import com.ken.rpc.entity.User;
import com.ken.rpc.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.util.ObjectUtils;

/**
 * 用户服务消费者控制器(HTTP接口)
 *
 * @author ken
 */
@Slf4j
@RestController
@RequestMapping("/user")
@Tag(name = "UserConsumerController", description = "用户服务消费者接口,通过RPC调用用户服务")
public class UserConsumerController {

    /**
     * 注入UserService RPC代理对象(通过@RpcReference自动生成)
     */
    @RpcReference(interfaceClass = UserService.class, serviceVersion = "1.0.0", registryAddress = "127.0.0.1:2181")
    private UserService userService;

    /**
     * HTTP接口:根据用户ID查询用户(调用RPC服务)
     *
     * @param userId 用户ID
     * @return 用户信息
     */
    @GetMapping("/{userId}")
    @Operation(summary = "查询用户", description = "通过HTTP接口调用RPC服务查询用户")
    public String getUserById(
            @Parameter(description = "用户ID", required = true, example = "1001")
            @PathVariable Long userId) {
        try {
            log.info("消费者接收查询用户请求,userId:{}", userId);
            User user = userService.getUserById(userId);
            if (ObjectUtils.isEmpty(user)) {
                return "查询结果:用户不存在,userId:" + userId;
            }
            return "查询结果:" + user.toString();
        } catch (Exception e) {
            log.error("查询用户失败,userId:{},异常信息:{}", userId, e.getMessage(), e);
            return "查询失败:" + e.getMessage();
        }
    }

    /**
     * HTTP接口:创建用户(调用RPC服务)
     *
     * @param user 用户信息
     * @return 创建结果
     */
    @PostMapping("/create")
    @Operation(summary = "创建用户", description = "通过HTTP接口调用RPC服务创建用户")
    public String createUser(
            @Parameter(description = "用户信息", required = true)
            @RequestBody User user) {
        try {
            log.info("消费者接收创建用户请求,用户信息:{}", user);
            Long userId = userService.createUser(user);
            return "创建成功:新用户ID=" + userId;
        } catch (Exception e) {
            log.error("创建用户失败,用户信息:{},异常信息:{}", user, e.getMessage(), e);
            return "创建失败:" + e.getMessage();
        }
    }
}
(2)服务消费者配置文件(application.yml)
复制代码
# Spring配置
spring:
  application:
    name: user-consumer

# RPC框架配置
rpc:
  registry:
    address: 127.0.0.1:2181 # ZooKeeper注册中心地址(全局配置)

# 服务器端口(避免与服务提供者冲突)
server:
  port: 8081

# Swagger3配置
springdoc:
  api-docs:
    path: /v3/api-docs
  swagger-ui:
    path: /swagger-ui.html
    operationsSorter: method
  packages-to-scan: com.ken.rpc.consumer.controller
(3)服务消费者启动类
复制代码
package com.ken.rpc.consumer;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;

/**
 * RPC服务消费者启动类(用户服务)
 *
 * @author ken
 */
@SpringBootApplication(scanBasePackages = "com.ken.rpc")
@EntityScan("com.ken.rpc.entity") // 扫描实体类
@OpenAPIDefinition(info = @Info(title = "User Consumer API", version = "1.0", description = "用户服务消费者接口文档"))
public class UserConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserConsumerApplication.class, args);
    }
}

5. 框架测试与验证

5.1 环境准备

  1. 启动 ZooKeeper :下载 ZooKeeper 3.9.2,解压后修改conf/zoo.cfg,设置dataDir,执行bin/zkServer.sh start(Linux)或bin/zkServer.cmd start(Windows)。
  2. 创建数据库 :在 MySQL 8.0 中创建rpc_db数据库,执行 4.6.5.2 中的 SQL 脚本创建user表并插入测试数据。
  3. 构建项目 :在父项目根目录执行mvn clean install,确保所有模块编译通过。

5.2 启动与测试步骤

步骤 1:启动服务提供者
  1. 运行UserProviderApplicationmain方法。
  2. 查看日志,确认以下信息:
    • Netty 服务端启动成功(监听端口 8080)。
    • 服务注册到 ZooKeeper 成功(服务 key:com.ken.rpc.service.UserService:1.0.0)。
步骤 2:启动服务消费者
  1. 运行UserConsumerApplicationmain方法。
  2. 查看日志,确认以下信息:
    • RPC 代理对象生成成功(UserService代理)。
    • 代理对象注入到UserConsumerController成功。
步骤 3:通过 Swagger3 测试接口
  1. 访问消费者 Swagger 地址:http://localhost:8081/swagger-ui.html
  2. 测试 "查询用户" 接口:
    • 展开/user/{userId}接口,输入userId=1001,点击 "Try it out"。
    • 预期结果:返回zhangsan的用户信息。
  3. 测试 "创建用户" 接口:
    • 展开/user/create接口,输入请求体:

      json

      复制代码
      {
        "username": "wangwu",
        "age": 28,
        "email": "wangwu@example.com"
      }
    • 点击 "Try it out",预期结果:返回新用户 ID(如 1003)。

  4. 验证数据库:查询user表,确认wangwu的记录已插入。

5.3 测试结果分析

  • 正确性:RPC 调用成功,服务消费者能通过代理对象调用远程服务,数据交互正确。
  • 透明性:消费者无需关注网络通信、序列化、服务发现等细节,像调用本地方法一样调用远程服务。
  • 可用性:服务提供者下线后,ZooKeeper 会自动删除临时节点,消费者后续调用会选择其他可用节点(需部署多个提供者测试)。

6. 框架优化与扩展方向

当前实现的 RPC 框架已具备核心功能,但在生产环境中还需优化和扩展,以下是常用方向:

6.1 容错机制

  • 重试机制 :调用失败时自动重试(如网络抖动),可通过Guava-Retryer实现,支持重试次数、重试间隔配置。
  • 服务降级 :当服务不可用时,返回默认值或友好提示(如Resilience4j的降级功能)。
  • 熔断机制 :服务连续失败次数达到阈值时,暂时停止调用,避免雪崩效应(如Resilience4j的熔断功能)。

6.2 性能优化

  • 序列化优化:替换 FastJSON2 为 Protobuf,Protobuf 是二进制序列化协议,体积更小、速度更快,适合高性能场景。
  • 连接池优化:Netty 客户端使用连接池,避免频繁创建和关闭连接,减少开销。
  • 异步调用:支持 CompletableFuture 异步调用,避免同步调用阻塞线程,提高并发量。

6.3 功能扩展

  • 多注册中心支持 :实现 Nacos、Etcd 等注册中心的ServiceRegistry接口,用户可通过配置切换。
  • 服务监控:集成 SkyWalking 或 Prometheus,监控 RPC 调用耗时、成功率、并发量等指标。
  • 权限控制:在 RPC 请求中添加 token,服务端验证 token 合法性,防止非法调用。
  • 服务文档自动生成:基于服务接口注解,自动生成 RPC 服务文档(类似 Swagger)。

6.4 代码示例:添加重试机制

RpcProxyFactory为例,集成Guava-Retryer实现重试:

复制代码
// 添加Guava Retryer依赖
<dependency>
    <groupId>com.github.rholder</groupId>
    <artifactId>guava-retrying</artifactId>
    <version>2.0.0</version>
</dependency>

// 在RpcProxyFactory的invoke方法中添加重试逻辑
Retryer<RpcResponse> retryer = RetryerBuilder.<RpcResponse>newBuilder()
        .retryIfExceptionOfType(NetworkException.class) // 网络异常重试
        .retryIfResult(response -> !response.isSuccess()) // 调用失败重试
        .withStopStrategy(StopStrategies.stopAfterAttempt(3)) // 最多重试3次
        .withWaitStrategy(WaitStrategies.fixedWait(100, TimeUnit.MILLISECONDS)) // 重试间隔100ms
        .build();

try {
    // 带重试的调用
    RpcResponse rpcResponse = retryer.call(() -> clientTransport.sendRequest(rpcRequest).get());
} catch (ExecutionException e) {
    // 重试失败处理
    throw new RpcException("RPC调用重试失败:" + e.getCause().getMessage(), e.getCause());
}

7. 总结

本文从 0 到 1 实现了一个可运行的 RPC 框架,核心包含序列化层、网络传输层、服务注册中心、动态代理层、负载均衡层、注解驱动层六大模块,通过示例验证了框架的正确性和可用性。

  • 核心逻辑:通过动态代理屏蔽远程调用细节,Netty 实现高性能通信,ZooKeeper 实现服务发现,负载均衡实现服务分发,注解驱动简化用户使用。
  • 关键收获 :理解 RPC 的底层原理(如 TCP 粘包拆包、序列化、服务发现),掌握框架设计的 "接口化" 思想(如SerializerServiceRegistry),便于后续扩展。
  • 生产落地:若需在生产环境使用,需补充容错、监控、权限等功能,并进行性能测试和压测,确保满足业务需求。
相关推荐
脸大是真的好~3 小时前
黑马JAVAWeb-01 Maven依赖管理-生命周期-单元测试
java·maven
zhangkaixuan4564 小时前
Apache Paimon 查询全流程深度分析
java·apache·paimon
cici158745 小时前
MyBatis注解的运用于条件搜索实践
java·tomcat·mybatis
wangqiaowq5 小时前
StarRocks安装部署测试
java·开发语言
计算机学姐5 小时前
基于SpringBoot的高校社团管理系统【协同过滤推荐算法+数据可视化】
java·vue.js·spring boot·后端·mysql·信息可视化·推荐算法
fei_sun9 小时前
【复习】计网每日一题1105大题---ARP、NAT、路由器、IP数据报、冲突域、广播域、100BASE-F、10BASE-T
网络
工业甲酰苯胺9 小时前
实现 json path 来评估函数式解析器的损耗
java·前端·json
老前端的功夫9 小时前
Web应用的永生之术:PWA落地与实践深度指南
java·开发语言·前端·javascript·css·node.js
@forever@9 小时前
【JAVA】LinkedList与链表
java·python·链表