摘要:这篇文章带你系统理解 RPC 的本质与 HTTP API 的差异,掌握动态代理、序列化、TCP 粘包/半包等关键知识点,并在 Spring Boot 项目中完成一个“能跑”的最小 RPC:JDK 动态代理 + Socket + 原生序列化。文章最后给出演进路线(可插拔序列化、自定义协议头、Netty、注册中心、负载均衡、熔断与治理)。
源码:GitHub 仓库 —— https://github.com/Wilsoncyf/my-rpc-mini ([GitHub][1])


目录

  1. 什么是 RPC?为何不是“更花哨的 HTTP”
  2. 核心原理速通:代理、序列化、协议、传输、治理
  3. 我们要做什么:最小可运行版目标与边界
  4. 工程结构与关键代码(Socket + 动态代理)
  5. 运行验证:启动、调用与输出

1. 什么是 RPC?为何不是“更花哨的 HTTP”

RPC(Remote Procedure Call)追求的是:让远程方法调用看起来“像本地方法”。而 HTTP API(如 OpenFeign)强调的是资源/路由 + JSON 的开放性与通用性。

差异一览:

维度 RPC HTTP API(Feign/RestTemplate)
契约形式 接口/IDL(方法粒度、强类型) URL/动词/JSON(资源粒度)
传输 多为 TCP + 二进制,长连接 HTTP/1.1/2 + JSON
延迟/QPS 追求低延迟/高吞吐 通用与开放优先
服务治理 常内置 LB、超时、重试、熔断 通过生态组合(LoadBalancer、Resilience4j)
适用场景 内部高频核心链路 对外/跨团队/开放集成

一句话Feign 解决“写 HTTP 很麻烦”,RPC 解决“HTTP 不够快、不够强、不够可控”。


2. 核心原理速通:代理、序列化、协议、传输、治理

一次 RPC 的完整链路

业务调用方 动态代理(Stub) 序列化/协议打包 TCP/Socket/Netty 服务端分发 真实实现 userService.getUser(1) 封装: 接口/方法/参数类型/参数值 字节流请求(带协议头) 长连接传输 反序列化后反射调用 返回结果 字节流响应(带requestId) 读包/拆包 反序列化为对象 返回 User 业务调用方 动态代理(Stub) 序列化/协议打包 TCP/Socket/Netty 服务端分发 真实实现

为何需要这些要素?

  • 动态代理:拦截 interface 方法 → 变成网络请求;
  • 序列化:对象 ↔ byte[](性能与体积直连 QoS);
  • 协议头:长度、类型、requestId,解决粘包/半包与并发回包匹配;
  • 传输层:长连接、心跳、连接池、线程模型(I/O 与业务分离);
  • 治理:超时、重试(幂等读)、熔断、限流、降级。

3. 我们要做什么:最小可运行版目标与边界

目标:在 Spring Boot 工程中打通最小闭环

  • 客户端:JDK 动态代理拦截接口方法,组装请求,经 Socket 发送;
  • 服务端:收到请求后反射调用实现类,返回结果。

边界(刻意简化)

  • 暂用 Java 原生序列化(方便打通,但不适合生产);
  • 单线程 Server(演示用,后续会改为线程池 + Netty);
  • 无协议头(下一版加 magic/version/serializeType/requestId/bodyLength 等)。

4. 工程结构与关键代码(Socket + 动态代理)

仓库地址:https://github.com/Wilsoncyf/my-rpc-mini ([GitHub][1])

目录结构(核心包):

com.wilson.myrpcmini
├── api        # 对外暴露的接口与数据模型
├── core       # 动态代理、请求/响应、Client/Server
└── demo       # Spring Boot Runner + 示例 Controller

调用视角图

结果
业务代码
UserService 接口
ProxyFactory 动态代理
RpcClient Socket实现
RpcServer 反射分发
UserServiceImpl

下面挑关键点摘录(完整请见仓库),每段都尽量“只做一件事”,利于后续模块化演进。

① 动态代理:把“方法调用”变成“网络请求”

public class ProxyFactory {
    private final RpcClient client;
    public ProxyFactory(RpcClient client) { this.client = client; }

    @SuppressWarnings("unchecked")
    public <T> T createProxy(Class<T> interfaceClass) {
        return (T) Proxy.newProxyInstance(
            interfaceClass.getClassLoader(),
            new Class<?>[]{interfaceClass},
            (proxy, method, args) -> {
                RpcRequest req = new RpcRequest(
                    interfaceClass.getName(),
                    method.getName(),
                    method.getParameterTypes(),
                    args
                );
                return client.invoke(req); // 真正发起远程调用
            }
        );
    }
}

② 客户端 Socket 调用:一次请求一次连接(演示用)

public class RpcClient {
    private final String host;
    private final int port;
    public RpcClient(String host, int port) { this.host = host; this.port = port; }

    public Object invoke(RpcRequest req) throws Exception {
        try (Socket socket = new Socket(host, port)) {
            socket.setTcpNoDelay(true);
            try (ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                 ObjectInputStream  ois = new ObjectInputStream(socket.getInputStream())) {
                oos.writeObject(req);
                oos.flush();
                RpcResponse resp = (RpcResponse) ois.readObject();
                if (resp.isOk()) return resp.getResult();
                throw new RuntimeException("RPC Error: " + resp.getError());
            }
        }
    }
}

③ 服务端单线程分发:反射执行后回写结果

public class RpcServer {
    private final int port;
    private final Map<String, Object> serviceTable = new ConcurrentHashMap<>();

    public <T> void addService(Class<T> iface, T impl) {
        serviceTable.put(iface.getName(), impl); // 映射接口 -> 实现
    }

    public void start() throws Exception {
        try (ServerSocket server = new ServerSocket(port)) {
            System.out.println("[RpcServer] listening on " + port);
            while (true) {
                try (Socket client = server.accept();
                     ObjectInputStream  ois = new ObjectInputStream(client.getInputStream());
                     ObjectOutputStream oos = new ObjectOutputStream(client.getOutputStream())) {

                    RpcRequest req = (RpcRequest) ois.readObject();
                    Object impl = serviceTable.get(req.getInterfaceName());
                    RpcResponse resp;
                    if (impl == null) {
                        resp = RpcResponse.error("No service: " + req.getInterfaceName());
                    } else {
                        try {
                            Method m = impl.getClass().getMethod(req.getMethodName(), req.getParamTypes());
                            Object ret = m.invoke(impl, req.getArgs());
                            resp = RpcResponse.ok(ret);
                        } catch (Throwable t) {
                            resp = RpcResponse.error(t.toString());
                        }
                    }
                    oos.writeObject(resp);
                    oos.flush();
                }
            }
        }
    }
}

④ 在 Spring Boot 中嵌入 RPC Server(方便一键跑通)

@Component
public class RpcServerRunner implements CommandLineRunner {
    @Override
    public void run(String... args) {
        new Thread(() -> {
            try {
                RpcServer server = new RpcServer(9090);
                server.addService(UserService.class, new UserServiceImpl());
                server.start();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "rpc-server-thread").start();
    }
}

⑤ 暴露一个 REST 接口,触发一次“像本地方法”的远程调用

@RestController
public class RpcDemoController {
    @GetMapping("/rpc/hello")
    public Object hello(@RequestParam(defaultValue = "1") Integer id) throws Exception {
        RpcClient client = new RpcClient("127.0.0.1", 9090);
        ProxyFactory factory = new ProxyFactory(client);
        UserService userService = factory.createProxy(UserService.class);
        return userService.getUser(id); // 看起来是本地方法,实则跨进程
    }
}

5. 运行验证:启动、调用与输出

步骤:

# 构建/启动
mvn spring-boot:run

# 触发一次 RPC
curl "http://localhost:8080/rpc/hello?id=2"

预期输出:

{"id":2,"name":"Wilson-2"}

时序回放图:

/rpc/hello JDK动态代理 RpcClient(Socket) RpcServer UserServiceImpl UserService.getUser(2) 组装 RpcRequest Socket 发送请求 反射调用 getUser(2) 返回 User(2) 写回 RpcResponse 返回 JSON /rpc/hello JDK动态代理 RpcClient(Socket) RpcServer UserServiceImpl

源码与 README:**https://github.com/Wilsoncyf/my-rpc-mini**(持续更新演进版本) ([GitHub][1])

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐