Java RPC原理
RPC(Remote Procedure Call)让远程服务调用像本地调用一样简单。
RPC核心概念
RPC是一种技术思想,允许程序调用另一台计算机上的方法,如同调用本地方法。
本地调用 vs RPC
Java
// 本地调用
UserService userService = new UserService();
User user = userService.getUser(1);
// RPC调用(看起来一样)
UserService userService = rpcClient.getService(UserService.class);
User user = userService.getUser(1);
RPC核心组件
| 组件 | 作用 |
|---|---|
| 客户端 | 发起调用,组装请求 |
| 客户端代理 | 生成代理对象,封装调用细节 |
| 序列化 | 对象转换为字节流 |
| 网络传输 | 发送请求/接收响应 |
| 服务端 | 接收请求,执行方法 |
| 反射 | 根据请求调用实际方法 |
| 反序列化 | 字节流还原为对象 |
RPC调用流程
Java
客户端 服务端
| |
|── 1.调用代理对象方法 ──────────|
| |
|── 2.序列化请求参数 ────────────|
| |
|── 3.网络发送请求 ─────────────→| 4.接收请求
| |
| |── 5.反序列化
| |── 6.反射调用
| |── 7.序列化结果
| |
|←── 8.网络返回响应 ─────────────|
| |
|── 9.反序列化结果 ──────────────|
| |
动态代理实现
Java
public class RpcProxy implements InvocationHandler {
private String host;
private int port;
public RpcProxy(String host, int port) {
this.host = host;
this.port = port;
}
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<T> clazz) {
return (T) Proxy.newProxyInstance(
clazz.getClassLoader(),
new Class<?>[]{clazz},
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 组装请求
RpcRequest request = new RpcRequest();
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParamTypes(method.getParameterTypes());
request.setParams(args);
// 发送请求
RpcResponse response = sendRequest(request);
// 返回结果
if (response.getError() != null) {
throw response.getError();
}
return response.getResult();
}
private RpcResponse sendRequest(RpcRequest request) throws Exception {
Socket socket = new Socket(host, port);
// 序列化并发送
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
out.writeObject(request);
// 接收并反序列化
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
RpcResponse response = (RpcResponse) in.readObject();
socket.close();
return response;
}
}
// 使用代理
UserService userService = new RpcProxy("localhost", 8888)
.getProxy(UserService.class);
User user = userService.getUser(1);
服务端实现
Java
public class RpcServer {
private int port;
public RpcServer(int port) {
this.port = port;
}
public void start() throws IOException {
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
Socket socket = serverSocket.accept();
new Thread(() -> handle(socket)).start();
}
}
private void handle(Socket socket) {
try {
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
RpcRequest request = (RpcRequest) in.readObject();
// 反射调用
Object result = invoke(request);
// 返回响应
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
RpcResponse response = new RpcResponse(result);
out.writeObject(response);
} catch (Exception e) {
e.printStackTrace();
}
}
private Object invoke(RpcRequest request) throws Exception {
Class<?> clazz = Class.forName(request.getClassName());
Object instance = clazz.getDeclaredConstructor().newInstance();
Method method = clazz.getMethod(request.getMethodName(), request.getParamTypes());
return method.invoke(instance, request.getParams());
}
}
序列化技术
| 序列化方式 | 优点 | 缺点 |
|---|---|---|
| Java原生 | 简单 | 效率低、体积大 |
| JSON | 通用、可读 | 效率中等 |
| Protobuf | 高效、体积小 | 需定义IDL |
| Hessian | 跨语言、效率高 | 不支持所有类型 |
| Kryo | 高效、体积小 | 仅Java |
注册中心
服务发现是RPC的关键组件:
text
// 服务注册
registry.register("UserService", "192.168.1.1:8888");
// 服务发现
List<String> addresses = registry.discover("UserService");
主流注册中心:Zookeeper、Nacos、Consul、Eureka
主流RPC框架
| 框架 | 特点 |
|---|---|
| Dubbo | 阿里开源,功能完善 |
| gRPC | Google开源,基于Protobuf |
| Thrift | Facebook开源,跨语言 |
| Hessian | 轻量级,跨语言 |
| Spring Cloud | HTTP协议,生态丰富 |
Dubbo架构
text
Consumer → Registry(注册中心) → Provider
↓ ↓ ↓
Monitor ←──────────────────────
核心角色:
- Provider:服务提供者
- Consumer:服务消费者
- Registry:注册中心
- Monitor:监控中心
注意事项
序列化要考虑跨语言兼容性
网络超时设置很重要,避免无限等待
服务注册与发现是集群部署的关键
负载均衡策略影响服务性能
异步调用提高并发性能
要点总结
- RPC让远程调用像本地调用一样简单
- 核心组件:代理、序列化、网络传输、反射
- 动态代理封装调用细节,用户无感知
- 序列化决定传输效率和跨语言能力
- 注册中心实现服务发现与负载均衡
📝 发现内容有误?点击此处直接编辑