从零实现一个轻量级RPC框架
1. 引言
在微服务架构盛行的今天,远程过程调用(Remote Procedure Call,RPC)已成为服务间通信的基本方式。成熟的RPC框架如Dubbo、gRPC、Thrift等,为我们提供了强大的功能,但它们的源码往往复杂且庞大,不利于初学者理解RPC的核心原理。本文将从零开始,手把手实现一个简易但功能完备的RPC框架,通过这个过程,你将深入理解RPC的本质——像调用本地方法一样调用远程服务。
我们将使用 Java 作为开发语言,基于 Netty 实现高性能网络通信,采用 JSON 作为序列化协议(方便调试),并设计一个轻量级的注册中心(基于内存)。最终,我们的框架将支持服务注册、动态代理、网络传输、负载均衡(简单轮询)和超时重试等核心特性。
2. RPC基本原理
RPC的核心思想是封装网络通信细节,让调用者感知不到远程调用与本地调用的区别。一次完整的RPC调用流程如下:
- 客户端 调用本地代理对象(Stub)的方法。
- 代理对象 将方法名、参数类型、参数值等信息封装成请求消息。
- 客户端 通过网络将请求消息发送给服务端。
- 服务端 接收请求,根据方法名和参数类型找到对应的服务实现。
- 服务端 执行该方法,并将返回值封装成响应消息。
- 服务端 将响应消息发送回客户端。
- 客户端 解析响应,返回给调用者。
在这个过程中,几个关键技术点:
- 动态代理:生成代理对象,拦截本地方法调用。
- 序列化/反序列化:将Java对象转换成字节流以便网络传输。
- 网络通信:高效、可靠的传输层实现。
- 服务注册与发现:让客户端知道服务端的位置(IP和端口)。
3. 技术选型
| 组件 | 选型 | 理由 |
|---|---|---|
| 开发语言 | Java 8 | 生态成熟,企业级应用广泛 |
| 网络通信框架 | Netty 4.x | 异步事件驱动,高性能,易于定制 |
| 序列化协议 | Fastjson / Jackson | 纯文本,可读性强,开发调试方便 |
| 注册中心 | 内存版(ConcurrentHashMap) | 简化实现,核心逻辑通用,可后续扩展 |
| 动态代理 | JDK动态代理 | 基于接口,简单轻量 |
当然,生产环境可以使用Protobuf或Hessian提高性能,使用ZooKeeper或Nacos作为注册中心,但本文侧重于原理,故采用最简方案。
4. 整体架构设计
我们的框架分为三个模块:
- rpc-common:公共模块,包含请求/响应对象、序列化接口、常量等。
- rpc-server:服务端模块,负责启动服务、注册服务、处理请求。
- rpc-client:客户端模块,负责动态代理、连接管理、发送请求。
架构图如下(文本描述):
text
复制
下载
+------------------+ +------------------+
| Client App | | Server App |
| (本地调用) | | (服务实现) |
+--------+---------+ +--------+---------+
| |
v v
+------------------+ +------------------+
| Dynamic Proxy | | Service Registry|
| (封装请求) | | (本地Map) |
+--------+---------+ +--------+---------+
| |
v v
+------------------+ +------------------+
| Netty Client |<------>| Netty Server |
| (发送/接收) | | (接收/发送) |
+------------------+ +------------------+
通信协议:我们定义简单的二进制协议,包含魔数、消息长度、消息体(JSON字节) 。这样便于处理粘包/拆包。
5. 核心模块实现
5.1 项目结构
text
复制
下载
rpc-framework/
├── rpc-common/
│ ├── src/main/java/com/rpc/
│ │ ├── model/ // RpcRequest, RpcResponse
│ │ ├── serializer/ // Serializer接口, JsonSerializer
│ │ └── constants/ // 常量
├── rpc-server/
│ ├── src/main/java/com/rpc/
│ │ ├── server/ // RpcServer, ServiceRegistry
│ │ ├── handler/ // RpcServerHandler
│ │ └── bootstrap/ // ServerBootstrap
└── rpc-client/
├── src/main/java/com/rpc/
│ ├── proxy/ // RpcProxy, ProxyFactory
│ ├── client/ // RpcClient, NettyClient
│ ├── handler/ // RpcClientHandler
│ └── loadbalance/ // LoadBalancer
5.2 公共模块:定义请求与响应
RpcRequest.java
java
复制
下载
package com.rpc.model;
import java.io.Serializable;
import java.util.Arrays;
public class RpcRequest implements Serializable {
private static final long serialVersionUID = 1L;
private String requestId; // 唯一ID,用于异步回调匹配
private String interfaceName; // 接口全限定名
private String methodName;
private Class<?>[] parameterTypes;
private Object[] parameters;
// getter/setter 省略
@Override
public String toString() {
return "RpcRequest{" +
"requestId='" + requestId + ''' +
", interfaceName='" + interfaceName + ''' +
", methodName='" + methodName + ''' +
", parameterTypes=" + Arrays.toString(parameterTypes) +
", parameters=" + Arrays.toString(parameters) +
'}';
}
}
RpcResponse.java
java
复制
下载
package com.rpc.model;
import java.io.Serializable;
public class RpcResponse implements Serializable {
private static final long serialVersionUID = 1L;
private String requestId;
private Object result; // 正常返回结果
private Throwable error; // 异常信息
public boolean isError() {
return error != null;
}
// getter/setter 省略
}
5.3 序列化模块
我们定义接口,并实现基于Fastjson的序列化。
Serializer.java
java
复制
下载
package com.rpc.serializer;
public interface Serializer {
<T> byte[] serialize(T obj);
<T> T deserialize(byte[] data, Class<T> clazz);
}
JsonSerializer.java
java
复制
下载
package com.rpc.serializer;
import com.alibaba.fastjson.JSON;
public class JsonSerializer implements Serializer {
@Override
public <T> byte[] serialize(T obj) {
return JSON.toJSONString(obj).getBytes();
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) {
return JSON.parseObject(new String(data), clazz);
}
}
5.4 网络通信模块(基于Netty)
我们使用Netty构建客户端和服务端,并自定义编解码器。
5.4.1 协议定义
为了处理粘包/拆包,我们定义消息头(Header):
- 魔数:4字节,固定为
0xCAFEBABE,用于校验。 - 消息长度:4字节,表示消息体长度(字节数)。
- 消息体:JSON序列化后的字节数组。
MessageEncoder.java(服务端和客户端共用)
java
复制
下载
package com.rpc.codec;
import com.rpc.serializer.Serializer;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class MessageEncoder extends MessageToByteEncoder<Object> {
private final Serializer serializer;
public MessageEncoder(Serializer serializer) {
this.serializer = serializer;
}
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
byte[] body = serializer.serialize(msg);
// 魔数
out.writeInt(0xCAFEBABE);
// 消息长度
out.writeInt(body.length);
// 消息体
out.writeBytes(body);
}
}
MessageDecoder.java
java
复制
下载
package com.rpc.codec;
import com.rpc.serializer.Serializer;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class MessageDecoder extends ByteToMessageDecoder {
private final Serializer serializer;
private final Class<?> targetClass;
public MessageDecoder(Serializer serializer, Class<?> targetClass) {
this.serializer = serializer;
this.targetClass = targetClass;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// 确保有足够字节读取魔数和长度
if (in.readableBytes() < 8) return;
in.markReaderIndex();
int magic = in.readInt();
if (magic != 0xCAFEBABE) {
throw new RuntimeException("Invalid magic number");
}
int length = in.readInt();
if (in.readableBytes() < length) {
in.resetReaderIndex();
return;
}
byte[] body = new byte[length];
in.readBytes(body);
Object obj = serializer.deserialize(body, targetClass);
out.add(obj);
}
}
5.4.2 服务端启动
RpcServer.java
java
复制
下载
package com.rpc.server;
import com.rpc.codec.MessageDecoder;
import com.rpc.codec.MessageEncoder;
import com.rpc.handler.RpcServerHandler;
import com.rpc.model.RpcRequest;
import com.rpc.serializer.JsonSerializer;
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;
public class RpcServer {
private final int port;
private final ServiceRegistry registry;
public RpcServer(int port) {
this.port = port;
this.registry = new ServiceRegistry();
}
public void registerService(Class<?> interfaceClass, Object serviceImpl) {
registry.register(interfaceClass.getName(), serviceImpl);
}
public void start() throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MessageDecoder(new JsonSerializer(), RpcRequest.class));
pipeline.addLast(new MessageEncoder(new JsonSerializer()));
pipeline.addLast(new RpcServerHandler(registry));
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.bind(port).sync();
System.out.println("RPC Server started on port " + port);
future.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
ServiceRegistry.java
java
复制
下载
package com.rpc.server;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ServiceRegistry {
private final Map<String, Object> serviceMap = new ConcurrentHashMap<>();
public void register(String serviceName, Object service) {
serviceMap.put(serviceName, service);
}
public Object getService(String serviceName) {
return serviceMap.get(serviceName);
}
}
5.4.3 服务端请求处理器
RpcServerHandler.java
java
复制
下载
package com.rpc.handler;
import com.rpc.model.RpcRequest;
import com.rpc.model.RpcResponse;
import com.rpc.server.ServiceRegistry;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.lang.reflect.Method;
public class RpcServerHandler extends SimpleChannelInboundHandler<RpcRequest> {
private final ServiceRegistry registry;
public RpcServerHandler(ServiceRegistry registry) {
this.registry = registry;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, RpcRequest request) throws Exception {
RpcResponse response = new RpcResponse();
response.setRequestId(request.getRequestId());
try {
Object service = registry.getService(request.getInterfaceName());
if (service == null) {
throw new RuntimeException("Service not found: " + request.getInterfaceName());
}
Method method = service.getClass().getMethod(request.getMethodName(), request.getParameterTypes());
Object result = method.invoke(service, request.getParameters());
response.setResult(result);
} catch (Exception e) {
response.setError(e);
}
ctx.writeAndFlush(response);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
5.5 客户端实现
客户端需要动态代理,将方法调用转化为网络请求,并同步等待响应(使用CompletableFuture或CountDownLatch实现同步)。
5.5.1 客户端启动与连接管理
RpcClient.java
java
复制
下载
package com.rpc.client;
import com.rpc.codec.MessageDecoder;
import com.rpc.codec.MessageEncoder;
import com.rpc.handler.RpcClientHandler;
import com.rpc.model.RpcResponse;
import com.rpc.serializer.JsonSerializer;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.net.InetSocketAddress;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class RpcClient {
private final String host;
private final int port;
private Channel channel;
private final ConcurrentHashMap<String, CompletableFuture<RpcResponse>> pendingRequests = new ConcurrentHashMap<>();
private final RpcClientHandler handler = new RpcClientHandler(pendingRequests);
public RpcClient(String host, int port) {
this.host = host;
this.port = port;
}
public void connect() throws InterruptedException {
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new MessageDecoder(new JsonSerializer(), RpcResponse.class));
ch.pipeline().addLast(new MessageEncoder(new JsonSerializer()));
ch.pipeline().addLast(handler);
}
});
ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port)).sync();
channel = future.channel();
System.out.println("Connected to server " + host + ":" + port);
}
public void sendRequest(RpcRequest request, CompletableFuture<RpcResponse> future) {
pendingRequests.put(request.getRequestId(), future);
channel.writeAndFlush(request);
}
public void close() {
if (channel != null) {
channel.close();
}
}
}
5.5.2 客户端处理器
RpcClientHandler.java
java
复制
下载
package com.rpc.handler;
import com.rpc.model.RpcResponse;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
public class RpcClientHandler extends SimpleChannelInboundHandler<RpcResponse> {
private final ConcurrentHashMap<String, CompletableFuture<RpcResponse>> pendingRequests;
public RpcClientHandler(ConcurrentHashMap<String, CompletableFuture<RpcResponse>> pendingRequests) {
this.pendingRequests = pendingRequests;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, RpcResponse response) throws Exception {
String requestId = response.getRequestId();
CompletableFuture<RpcResponse> future = pendingRequests.remove(requestId);
if (future != null) {
future.complete(response);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
5.5.3 动态代理与远程调用
RpcProxy.java
java
复制
下载
package com.rpc.proxy;
import com.rpc.client.RpcClient;
import com.rpc.model.RpcRequest;
import com.rpc.model.RpcResponse;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class RpcProxy {
private final RpcClient client;
private final long timeout; // 超时时间(毫秒)
public RpcProxy(RpcClient client, long timeout) {
this.client = client;
this.timeout = timeout;
}
@SuppressWarnings("unchecked")
public <T> T createProxy(Class<T> interfaceClass) {
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class<?>[]{interfaceClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 如果调用Object方法,直接处理
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
RpcRequest request = new RpcRequest();
request.setRequestId(UUID.randomUUID().toString());
request.setInterfaceName(interfaceClass.getName());
request.setMethodName(method.getName());
request.setParameterTypes(method.getParameterTypes());
request.setParameters(args);
CompletableFuture<RpcResponse> future = new CompletableFuture<>();
client.sendRequest(request, future);
// 同步等待,带超时
RpcResponse response = future.get(timeout, TimeUnit.MILLISECONDS);
if (response.isError()) {
throw response.getError();
}
return response.getResult();
}
}
);
}
}
5.6 注册中心(简易版)
为简化,我们使用一个静态的 ConcurrentHashMap 存储服务地址列表,并实现轮询负载均衡。但为了保持本文聚焦于核心,我们暂不实现独立的注册中心,而是让客户端直接配置服务端地址。实际生产中,可以接入ZooKeeper,但其原理类似。
如果希望实现服务发现,可将上述 RpcClient 改造为从注册中心获取地址列表,并支持负载均衡策略。
6. 测试示例
现在,我们来构建一个简单的服务端和客户端进行测试。
6.1 定义服务接口
HelloService.java
java
复制
下载
package com.example;
public interface HelloService {
String sayHello(String name);
}
6.2 服务端实现
HelloServiceImpl.java
java
复制
下载
package com.example;
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "Hello, " + name;
}
}
ServerApp.java
java
复制
下载
package com.example;
import com.rpc.server.RpcServer;
public class ServerApp {
public static void main(String[] args) throws InterruptedException {
RpcServer server = new RpcServer(8080);
server.registerService(HelloService.class, new HelloServiceImpl());
server.start();
}
}
6.3 客户端调用
ClientApp.java
java
复制
下载
package com.example;
import com.rpc.client.RpcClient;
import com.rpc.proxy.RpcProxy;
public class ClientApp {
public static void main(String[] args) throws Exception {
RpcClient client = new RpcClient("127.0.0.1", 8080);
client.connect();
RpcProxy proxy = new RpcProxy(client, 3000);
HelloService helloService = proxy.createProxy(HelloService.class);
String result = helloService.sayHello("World");
System.out.println("Response: " + result); // 输出 Hello, World
client.close();
}
}
运行服务端,再运行客户端,即可看到调用成功。
7. 优化与扩展
上述实现虽然完整,但还有很多可以优化的地方:
- 序列化优化:JSON性能较差,可以替换为Protobuf、Kryo或Hessian,只需实现
Serializer接口即可。 - 连接池:客户端可维护多个连接,支持并发请求。
- 异步调用:当前是同步阻塞,可增加异步回调支持。
- 注册中心:集成ZooKeeper或Nacos,实现自动服务发现和健康检查。
- 负载均衡:多种策略(随机、一致性哈希等)。
- 超时重试:可配置重试次数和间隔。
- 链路追踪:添加TraceId,方便日志排查。
- 压缩:对大消息体进行GZIP压缩。
- 异常处理细化:区分业务异常和网络异常。
8. 总结
通过本文,我们从零实现了一个基于Netty + JSON的RPC框架,覆盖了服务注册、动态代理、网络通信、序列化等核心环节。尽管它还很简陋,但麻雀虽小五脏俱全,其设计思路与主流RPC框架一脉相承。阅读并实践本文后,你将不再对RPC感到神秘,能够更自信地使用和调优生产级框架。
希望这篇文章能成为你深入分布式系统领域的坚实起点。Happy coding!