从 0 到 1:用 Java 手写一个最小可运行的 RPC(Socket + 动态代理版)
摘要:这篇文章带你系统理解 RPC 的本质与 HTTP API 的差异,掌握动态代理、序列化、TCP 粘包/半包等关键知识点,并在 Spring Boot 项目中完成一个“能跑”的最小 RPC:JDK 动态代理 + Socket + 原生序列化。文章最后给出演进路线(可插拔序列化、自定义协议头、Netty、注册中心、负载均衡、熔断与治理)。
源码:GitHub 仓库 —— https://github.com/Wilsoncyf/my-rpc-mini ([GitHub][1])
目录
- 什么是 RPC?为何不是“更花哨的 HTTP”
- 核心原理速通:代理、序列化、协议、传输、治理
- 我们要做什么:最小可运行版目标与边界
- 工程结构与关键代码(Socket + 动态代理)
- 运行验证:启动、调用与输出
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 的完整链路:
为何需要这些要素?
- 动态代理:拦截
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
调用视角图:
下面挑关键点摘录(完整请见仓库),每段都尽量“只做一件事”,利于后续模块化演进。
① 动态代理:把“方法调用”变成“网络请求”
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"}
时序回放图:
源码与 README:**https://github.com/Wilsoncyf/my-rpc-mini**(持续更新演进版本) ([GitHub][1])
更多推荐



所有评论(0)