Skip to content

RPC 原理详解


RPC 是一种通信协议,允许程序调用另一个地址空间(通常是远程服务器)上的过程或函数,就像调用本地方法一样。

传统调用: RPC 调用:
Client ──► Server Client ──► 网络 ──► Server
(同一进程) (不同进程/机器)
特性RPCHTTP
协议层面传输层协议(TCP/UDP)应用层协议
序列化二进制(高效)JSON/XML(可读)
性能较低
耦合性强(需要双方约定格式)弱(跨语言)
使用场景内部微服务调用外部 API / 浏览器

┌─────────────────────────────────────────────────────────────────┐
│ RPC 调用完整流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Client Registry Server │
│ │ │ │ │
│ │ 1. 查询服务地址 │ │ │
│ │─────────────────────────>│ │ │
│ │<─────────────────────────│ │ │
│ │ │ │ │
│ │ 2. 序列化请求 │ │ │
│ │─────────────────────────>│──────────────────>│ │
│ │ │ │ │
│ │ │ 3. 反序列化 │ │
│ │ │ │────┐ │
│ │ │ │ │ │
│ │ │ 4. 定位服务 │ ▼ │
│ │ │ │ │
│ │ │ 5. 调用本地方法 │────┐ │
│ │ │ │ │ │
│ │ │ │<──── │
│ │ │ │ │
│ │ 6. 序列化响应 │ │ │
│ │<─────────────────────────<────────────────────│ │
│ │ │ │ │
│ │ 7. 反序列化,返回结果 │ │ │
│ │ │ │ │
└─────────────────────────────────────────────────────────────────┘
  1. 服务发现:Client 从注册中心获取服务提供者地址
  2. 序列化:将请求参数转换为字节数组
  3. 网络传输:通过 TCP/HTTP 发送请求
  4. 服务定位:Server 根据请求信息定位到具体服务
  5. 本地调用:执行本地方法调用
  6. 响应序列化:将返回值转换为字节数组
  7. 结果返回:网络传输 + 反序列化

Stub 是客户端的代理对象,负责:

  • 封装网络调用细节
  • 序列化请求参数
  • 反序列化响应结果
// 伪代码:Stub 的工作原理
public class UserServiceStub implements UserService {
private RPCClient client;
@Override
public User getUserById(Long id) {
// 1. 创建请求对象
RPCRequest request = new RPCRequest();
request.setMethodName("getUserById");
request.setParams(new Object[]{id});
// 2. 序列化请求
byte[] data = serialize(request);
// 3. 网络传输
byte[] response = client.send(data);
// 4. 反序列化响应
return (User) deserialize(response);
}
}

序列化是将对象转换为字节流的过程,反之则为反序列化。

常见序列化方式

方式优点缺点适用场景
Java 原生无依赖性能差、不跨语言内部使用
Hessian性能好、跨语言不支持循环引用Java 系
Kryo性能极高不跨语言大流量内部
Protobuf性能极高、跨语言需要 IDL 文件跨语言高性能
JSON可读、跨语言性能较差对外 API
  • TCP:面向连接、可靠,适用于高可靠性场景
  • HTTP/2:无状态、跨防火墙,适用于公网
  • 自定义协议:如 Dubbo 协议
  • 服务注册:Provider 启动时注册服务信息
  • 服务发现:Consumer 获取可用服务列表
  • 健康检查:剔除不健康的服务节点

public interface HelloService {
String sayHello(String name);
}
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "Hello, " + name + "!";
}
}
// RPC 服务器
public class RPCServer {
private Map<String, Object> serviceMap = new HashMap<>();
public void register(Object service) {
serviceMap.put(service.getClass().getInterfaces()[0].getName(), service);
}
public void start(int port) {
try (ServerSocket serverSocket = new ServerSocket(port)) {
while (true) {
Socket socket = serverSocket.accept();
// 处理请求(简化版)
new Thread(() -> handle(socket)).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void handle(Socket socket) {
try (ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream())) {
// 读取请求
String methodName = in.readUTF();
Object[] params = (Object[]) in.readObject();
// 查找服务
Object service = serviceMap.get(in.readUTF());
Method method = service.getClass().getMethod(methodName,
(Class<?>) params.getClass().getComponentType());
// 调用方法
Object result = method.invoke(service, params);
// 返回结果
out.writeObject(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class RPCClient {
public Object call(String serviceName, String methodName, Object[] params) {
try (Socket socket = new Socket("localhost", 8080);
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream in = new ObjectInputStream(socket.getInputStream())) {
// 发送请求
out.writeUTF(serviceName);
out.writeUTF(methodName);
out.writeObject(params);
// 接收响应
return in.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
// 客户端代理(Stub)
public class RPCProxy {
public <T> T create(Class<T> interfaceClass) {
return (T) Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class[]{interfaceClass},
(proxy, method, args) -> {
RPCClient client = new RPCClient();
return client.call(
interfaceClass.getName(),
method.getName(),
args
);
}
);
}
}

参考答案

  • RPC 是远程过程调用协议,强调的是”调用一个方法”
  • HTTP 是一种应用层协议,强调的是”传输数据”
  • RPC 通常使用 TCP 自定义协议,性能更高;HTTP 使用 JSON/XML,可读性好
  • RPC 需要双方约定接口格式,耦合度高;HTTP REST API 更灵活

参考答案

  • 静态发现:配置文件指定服务地址
  • 注册中心:使用 ZooKeeper / Nacos / Consul
  • DNS:通过 DNS 解析服务地址
  • 客户端缓存:本地缓存服务列表,定时更新

Q3: RPC 的序列化方式有哪些?如何选型?

Section titled “Q3: RPC 的序列化方式有哪些?如何选型?”

参考答案

  • Java 原生序列化:简单但性能差,有安全风险
  • Hessian:性能好,支持循环引用,但跨语言有限
  • Kryo:性能极高,不支持跨语言
  • Protobuf:性能极高,支持跨语言,需要定义 IDL
  • 选型建议:内部高性能调用用 Kryo/Protobuf,跨语言用 Protobuf,对外 API 用 JSON