RPC 原理详解
RPC 原理详解
Section titled “RPC 原理详解”RPC 核心概念
Section titled “RPC 核心概念”什么是 RPC?
Section titled “什么是 RPC?”RPC 是一种通信协议,允许程序调用另一个地址空间(通常是远程服务器)上的过程或函数,就像调用本地方法一样。
传统调用: RPC 调用:Client ──► Server Client ──► 网络 ──► Server(同一进程) (不同进程/机器)RPC 与 HTTP 的区别
Section titled “RPC 与 HTTP 的区别”| 特性 | RPC | HTTP |
|---|---|---|
| 协议层面 | 传输层协议(TCP/UDP) | 应用层协议 |
| 序列化 | 二进制(高效) | JSON/XML(可读) |
| 性能 | 高 | 较低 |
| 耦合性 | 强(需要双方约定格式) | 弱(跨语言) |
| 使用场景 | 内部微服务调用 | 外部 API / 浏览器 |
RPC 调用流程
Section titled “RPC 调用流程”┌─────────────────────────────────────────────────────────────────┐│ RPC 调用完整流程 │├─────────────────────────────────────────────────────────────────┤│ ││ Client Registry Server ││ │ │ │ ││ │ 1. 查询服务地址 │ │ ││ │─────────────────────────>│ │ ││ │<─────────────────────────│ │ ││ │ │ │ ││ │ 2. 序列化请求 │ │ ││ │─────────────────────────>│──────────────────>│ ││ │ │ │ ││ │ │ 3. 反序列化 │ ││ │ │ │────┐ ││ │ │ │ │ ││ │ │ 4. 定位服务 │ ▼ ││ │ │ │ ││ │ │ 5. 调用本地方法 │────┐ ││ │ │ │ │ ││ │ │ │<──── ││ │ │ │ ││ │ 6. 序列化响应 │ │ ││ │<─────────────────────────<────────────────────│ ││ │ │ │ ││ │ 7. 反序列化,返回结果 │ │ ││ │ │ │ │└─────────────────────────────────────────────────────────────────┘- 服务发现:Client 从注册中心获取服务提供者地址
- 序列化:将请求参数转换为字节数组
- 网络传输:通过 TCP/HTTP 发送请求
- 服务定位:Server 根据请求信息定位到具体服务
- 本地调用:执行本地方法调用
- 响应序列化:将返回值转换为字节数组
- 结果返回:网络传输 + 反序列化
RPC 核心组件
Section titled “RPC 核心组件”1. Stub(存根)
Section titled “1. Stub(存根)”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); }}2. Serialization(序列化)
Section titled “2. Serialization(序列化)”序列化是将对象转换为字节流的过程,反之则为反序列化。
常见序列化方式:
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Java 原生 | 无依赖 | 性能差、不跨语言 | 内部使用 |
| Hessian | 性能好、跨语言 | 不支持循环引用 | Java 系 |
| Kryo | 性能极高 | 不跨语言 | 大流量内部 |
| Protobuf | 性能极高、跨语言 | 需要 IDL 文件 | 跨语言高性能 |
| JSON | 可读、跨语言 | 性能较差 | 对外 API |
3. Transport(传输层)
Section titled “3. Transport(传输层)”- TCP:面向连接、可靠,适用于高可靠性场景
- HTTP/2:无状态、跨防火墙,适用于公网
- 自定义协议:如 Dubbo 协议
4. Registry(注册中心)
Section titled “4. Registry(注册中心)”- 服务注册:Provider 启动时注册服务信息
- 服务发现:Consumer 获取可用服务列表
- 健康检查:剔除不健康的服务节点
手写简单 RPC
Section titled “手写简单 RPC”1. 定义服务接口
Section titled “1. 定义服务接口”public interface HelloService { String sayHello(String name);}2. 实现服务端
Section titled “2. 实现服务端”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(); } }}3. 实现客户端
Section titled “3. 实现客户端”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 ); } ); }}面试高频问题
Section titled “面试高频问题”Q1: RPC 和 HTTP 调用有什么区别?
Section titled “Q1: RPC 和 HTTP 调用有什么区别?”参考答案:
- RPC 是远程过程调用协议,强调的是”调用一个方法”
- HTTP 是一种应用层协议,强调的是”传输数据”
- RPC 通常使用 TCP 自定义协议,性能更高;HTTP 使用 JSON/XML,可读性好
- RPC 需要双方约定接口格式,耦合度高;HTTP REST API 更灵活
Q2: RPC 如何实现服务发现?
Section titled “Q2: RPC 如何实现服务发现?”参考答案:
- 静态发现:配置文件指定服务地址
- 注册中心:使用 ZooKeeper / Nacos / Consul
- DNS:通过 DNS 解析服务地址
- 客户端缓存:本地缓存服务列表,定时更新
Q3: RPC 的序列化方式有哪些?如何选型?
Section titled “Q3: RPC 的序列化方式有哪些?如何选型?”参考答案:
- Java 原生序列化:简单但性能差,有安全风险
- Hessian:性能好,支持循环引用,但跨语言有限
- Kryo:性能极高,不支持跨语言
- Protobuf:性能极高,支持跨语言,需要定义 IDL
- 选型建议:内部高性能调用用 Kryo/Protobuf,跨语言用 Protobuf,对外 API 用 JSON