Netty-从零实现RPC框架

从零实现一个轻量级RPC框架

1. 引言

在微服务架构盛行的今天,远程过程调用(Remote Procedure Call,RPC)已成为服务间通信的基本方式。成熟的RPC框架如Dubbo、gRPC、Thrift等,为我们提供了强大的功能,但它们的源码往往复杂且庞大,不利于初学者理解RPC的核心原理。本文将从零开始,手把手实现一个简易但功能完备的RPC框架,通过这个过程,你将深入理解RPC的本质——像调用本地方法一样调用远程服务

我们将使用 Java 作为开发语言,基于 Netty 实现高性能网络通信,采用 JSON 作为序列化协议(方便调试),并设计一个轻量级的注册中心(基于内存)。最终,我们的框架将支持服务注册、动态代理、网络传输、负载均衡(简单轮询)和超时重试等核心特性。


2. RPC基本原理

RPC的核心思想是封装网络通信细节,让调用者感知不到远程调用与本地调用的区别。一次完整的RPC调用流程如下:

  1. 客户端 调用本地代理对象(Stub)的方法。
  2. 代理对象 将方法名、参数类型、参数值等信息封装成请求消息
  3. 客户端 通过网络将请求消息发送给服务端
  4. 服务端 接收请求,根据方法名和参数类型找到对应的服务实现
  5. 服务端 执行该方法,并将返回值封装成响应消息
  6. 服务端 将响应消息发送回客户端
  7. 客户端 解析响应,返回给调用者。

在这个过程中,几个关键技术点:

  • 动态代理:生成代理对象,拦截本地方法调用。
  • 序列化/反序列化:将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 客户端实现

客户端需要动态代理,将方法调用转化为网络请求,并同步等待响应(使用CompletableFutureCountDownLatch实现同步)。

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. 优化与扩展

上述实现虽然完整,但还有很多可以优化的地方:

  1. 序列化优化:JSON性能较差,可以替换为Protobuf、Kryo或Hessian,只需实现Serializer接口即可。
  2. 连接池:客户端可维护多个连接,支持并发请求。
  3. 异步调用:当前是同步阻塞,可增加异步回调支持。
  4. 注册中心:集成ZooKeeper或Nacos,实现自动服务发现和健康检查。
  5. 负载均衡:多种策略(随机、一致性哈希等)。
  6. 超时重试:可配置重试次数和间隔。
  7. 链路追踪:添加TraceId,方便日志排查。
  8. 压缩:对大消息体进行GZIP压缩。
  9. 异常处理细化:区分业务异常和网络异常。

8. 总结

通过本文,我们从零实现了一个基于Netty + JSON的RPC框架,覆盖了服务注册、动态代理、网络通信、序列化等核心环节。尽管它还很简陋,但麻雀虽小五脏俱全,其设计思路与主流RPC框架一脉相承。阅读并实践本文后,你将不再对RPC感到神秘,能够更自信地使用和调优生产级框架。

希望这篇文章能成为你深入分布式系统领域的坚实起点。Happy coding!