Java-RPC 是一个轻量级的分布式远程过程调用(RPC)框架,使用 Java 实现。它允许客户端像调用本地方法一样调用远端服务,框架自动处理网络通信、序列化、服务发现和负载均衡等底层细节。
| 组件 | 技术 | 版本 |
|---|---|---|
| 构建工具 | Maven 多模块 | - |
| 父框架 | Spring Boot | 3.3.2 |
| 网络通信 | Netty | 4.1.112.Final |
| 服务注册中心 | ZooKeeper + Curator | Curator 5.7.0 |
| 序列化 | Fastjson + Gson | Fastjson 1.2.67, Gson 2.11.0 |
| 语言 | Java | 22 |
| 工具库 | Lombok | 1.18.34 |
项目采用 Maven 多模块结构,共包含四个子模块:
java-rpc/
├── pom.xml # 父 POM
├── rpc-framework/ # 核心框架模块(接口定义 + 所有实现)
├── server-inter/ # 共享服务契约模块(服务接口 + 实体类)
├── client/ # 客户端应用入口
└── server/ # 服务端应用入口
| 模块 | 职责 |
|---|---|
| rpc-framework | RPC 核心基础设施:传输层实现、序列化、服务发现、负载均衡、动态代理等 |
| server-inter | 服务接口定义和数据实体,客户端和服务端共同依赖,确保类型安全 |
| client | 客户端应用入口,创建 ClientProxy 并通过代理发起 RPC 调用 |
| server | 服务端应用入口,注册服务实现并启动监听 |
- 动态代理生成:
ClientProxy使用 JDK 动态代理(java.lang.reflect.Proxy),拦截接口方法调用并封装为RPCRequest - 服务发现:通过
ZkServiceRegister从 ZooKeeper 中获取服务地址,支持负载均衡 - 网络传输:使用 Netty 异步 I/O 或原生 Socket 阻塞 I/O 传输数据
- 反射调用:服务端通过反射执行真实的服务方法并返回结果
封装一次远程调用所需的全部信息:
| 字段 | 类型 | 说明 |
|---|---|---|
interfaceName |
String |
服务接口全限定名 |
methodName |
String |
方法名 |
params |
Object[] |
方法参数列表 |
paramsTypes |
Class<?>[] |
参数类型列表 |
封装服务端返回的结果:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
int |
状态码(200=成功,500=失败) |
message |
String |
错误信息 |
data |
Object |
返回数据 |
dataType |
Class<?> |
数据类型(用于 JSON 反序列化时还原类型) |
所有客户端传输实现的顶层接口,定义了 sendRequest 方法。
使用 JDK 动态代理机制,将接口方法调用自动转化为 RPC 请求:
invoke()方法拦截所有代理对象的方法调用,构建RPCRequest并通过RPCClient发送getProxy()方法为指定接口生成代理实例
基于 Netty NIO 的高性能客户端实现:
- 使用
Bootstrap建立连接 - 通过
AttributeKey在 Channel 上存取响应结果(当前为同步阻塞等待) - 每次请求从 ZooKeeper 发现服务地址
基于 Java 原生 Socket 的阻塞式客户端,使用 ObjectInputStream/ObjectOutputStream 进行 Java 原生序列化通信。
所有服务端传输实现的顶层接口,定义了 start(int port) 和 stop() 方法。
本地服务注册表,管理 接口名 → 实现实例 的映射,并在 ZooKeeper 上注册服务地址:
provideServiceInterface()— 遍历实现类的所有接口,将接口名映射到实现实例,并在 ZooKeeper 注册getService()— 根据接口名获取实现实例
基于 Netty 的事件驱动服务端,使用 Boss/Worker 线程组模型。
基于 Java 原生 Socket + 线程池的服务端实现,每个连接分配一个 WorkThread 处理。
定义序列化/反序列化规范,支持通过 getSerializerByCode() 工厂方法获取实例:
| 编号 | 序列化方式 | 实现类 |
|---|---|---|
| 0 | Java 原生序列化 | ObjectSerializer |
| 1 | JSON 序列化 | JsonSerializer |
使用 Fastjson 进行序列化,反序列化时根据消息类型(Request/Response)进行不同的处理,通过 dataType 字段保留类型信息以解决 JSON 类型丢失问题。
使用 ObjectOutputStream/ObjectInputStream 实现,序列化字节中自带类型信息。
采用长度前缀 + 消息头的帧协议,解决 TCP 粘包/拆包问题:
+----------------+------------------+----------------+------------------+
| 消息类型 (2字节) | 序列化类型 (2字节) | 数据长度 (4字节) | 序列化数据 (N字节) |
+----------------+------------------+----------------+------------------+
- MyEncode(编码器):将
RPCRequest/RPCResponse对象按上述格式写入字节流 - MyDecode(解码器):从字节流中依次读取消息类型、序列化类型、数据长度和数据体,还原为对象
- MessageType(消息类型枚举):
REQUEST(0)和RESPONSE(1)
定义两个基本功能:注册服务地址、按服务名发现地址。
使用 Apache Curator 操作 ZooKeeper:
- 根路径:
/MyRPC - 注册:服务名创建为永久节点,服务地址创建为临时节点(服务下线自动删除)
- 发现:获取服务名下所有子节点(地址列表),通过负载均衡策略选取一个
- ZooKeeper 地址:
node1:32181,node2:32181,node3:32181
ZooKeeper 节点结构示例:
/MyRPC
├── /com.demo.service.UserService (永久节点)
│ └── /127.0.0.1:8899 (临时节点)
└── /com.demo.service.BlogService (永久节点)
└── /127.0.0.1:8899 (临时节点)
给定服务器地址列表,根据策略选择一个。
| 策略 | 实现类 | 说明 |
|---|---|---|
| 随机 | RandomLoadBalance |
随机选取一个地址 |
| 轮询 | RoundLoadBalance |
依次轮询,默认使用此策略 |
| 类 | 字段 | 说明 |
|---|---|---|
User |
userId, name |
用户实体 |
Blog |
id, name, desc |
博客实体 |
| 接口 | 方法 | 说明 |
|---|---|---|
UserService |
getUserById(String) |
根据 ID 查询用户 |
BlogService |
getBlogById(Integer) |
根据 ID 查询博客 |
getBlogs() |
获取博客列表 | |
printDate() |
无返回值测试方法 |
- JDK 22+
- Maven 3.x
- ZooKeeper 集群(默认地址:
node1:32181,node2:32181,node3:32181)
mvn clean install确保 ZooKeeper 集群已运行。如需修改连接地址,修改 ZkServiceRegister 中的 zkHosts 变量。
运行 server 模块中的 RPCServerApp.main()。
服务端会:
- 创建
UserServiceImpl和BlogServiceImpl实例 - 通过
ServiceProvider注册到 ZooKeeper(地址127.0.0.1:8899) - 启动 Netty 服务器监听 8899 端口
运行 client 模块中的 RPCClient.main()。
客户端会:
- 创建
ClientProxy(使用NettyRPCClient) - 通过代理调用远程服务方法
SocketChannel
└── MyDecode (解码器:字节流 → RPCResponse)
└── MyEncode (编码器:RPCRequest → 字节流)
└── NettyClientHandler (接收响应,存入 Channel Attribute)
SocketChannel
└── MyDecode (解码器:字节流 → RPCRequest)
└── MyEncode (编码器:RPCResponse → 字节流)
└── NettyRPCServerHandler (处理请求,反射调用服务)
- 在
server-inter模块中定义服务接口和实体类 - 在
server模块中编写实现类 - 在
RPCServerApp中注册实现:MyService myService = new MyServiceImpl(); serviceProvider.provideServiceInterface(myService);
- 在客户端通过代理调用:
MyService proxy = clientProxy.getProxy(MyService.class); proxy.someMethod();
- 实现
Serializer接口,分配新的类型编号 - 在
Serializer.getSerializerByCode()中注册
实现 LoadBalance 接口,并在 ZkServiceRegister 中替换 loadBalance 字段。
- 客户端:将
new NettyRPCClient()替换为new SimpleRPCClient() - 服务端:将
new NettyRPCServer(serviceProvider)替换为new ThreadPoolRPCRPCServer(serviceProvider)