RPC框架:核心概念与使用价值(新手易懂版)
RPC框架是分布式系统中的核心概念------简单来说,RPC(远程过程调用)框架是让你像调用本地函数一样调用「另一台服务器上的函数」的工具,无需关心网络通信、数据序列化等底层细节。
一、先搞懂:什么是RPC框架?
1. 核心定义(人话版)
- 本地调用 :你在Java代码里调用
Math.max(1,2),或在Python里调用list.append(3),都是「进程内」的函数调用,直接在内存中完成; - 远程调用:你的代码部署在服务器A,需要调用服务器B上的「用户余额查询函数」,这就是「跨机器」的远程调用;
- RPC框架 :封装了远程调用的所有底层细节(网络传输、数据格式转换、异常处理等),让你写
userService.getBalance(1001)时,感觉就像调用本地函数一样,完全感知不到「跨机器」的存在。
2. RPC的核心特征
| 特征 | 解释 |
|---|---|
| 透明性 | 调用远程函数 ≈ 调用本地函数,开发者无需关注网络、序列化等底层逻辑 |
| 跨语言/跨平台 | 比如Java服务可以调用Python服务,Windows服务器可以调用Linux服务器 |
| 高性能 | 相比HTTP接口(如RESTful),RPC通常采用更高效的序列化协议和通信模型 |
| 可扩展性 | 内置负载均衡、服务发现、熔断降级等分布式必备能力 |
3. RPC框架的工作流程(简化版)
用「客户端调用服务器B的余额查询函数」举例,RPC框架会自动完成以下步骤:
yaml
graph TD
A[客户端代码调用 getBalance(1001)] --> B[RPC客户端SDK]
B --> C[序列化参数:把1001转成二进制字节流]
C --> D[网络通信:通过TCP/UDP把字节流发给服务器B]
D --> E[RPC服务器端SDK]
E --> F[反序列化:把字节流转回1001]
F --> G[调用服务器B的本地getBalance函数]
G --> H[得到结果:比如99元]
H --> I[序列化结果:99转字节流]
I --> J[网络回传:把结果发给客户端]
J --> K[反序列化结果:字节流转99]
K --> L[客户端代码拿到99元]

整个过程中,开发者只需要写 getBalance(1001) 和处理返回值,其余步骤全由RPC框架自动完成。
二、什么时候不用RPC框架?
RPC不是万能的,以下场景优先用HTTP接口(如RESTful):
- 跨平台开放接口:比如给第三方开发者提供的接口(如微信支付API),HTTP+JSON更通用,无需对方接入你的RPC SDK;
- 简单的小项目:单机/少量服务的项目,远程调用少,没必要引入复杂的RPC框架;
- 需要浏览器访问的接口:浏览器只能发起HTTP请求,无法直接调用RPC接口。
三、主流RPC框架举例
不同语言/场景有不同的主流框架,核心功能类似:
| 框架 | 语言/特点 |
|---|---|
| Dubbo | 阿里开源,Java为主,国内微服务主流框架,适配Spring生态 |
| gRPC | Google开源,跨语言(C++/Java/Python/Go等),基于HTTP/2+Protobuf |
| Thrift | Facebook开源,跨语言,支持多种传输协议和序列化协议 |
| Spring Cloud OpenFeign | Java,基于HTTP的伪RPC(本质是HTTP封装),适配Spring Cloud生态 |
| Motan | 微博开源,Java/Go,支持多注册中心、跨语言 |
总结(核心要点)
- RPC框架的本质:封装远程调用的底层细节,让「跨机器调用函数」像「本地调用」一样简单;
- 使用RPC的核心原因 :
- 提效:避免手写网络、序列化等重复代码;
- 稳性:内置服务发现、熔断、负载均衡等分布式能力;
- 高性能:更优的通信/序列化协议,适配高并发;
- 适配微服务:支撑大规模服务间的远程调用;
- RPC vs HTTP:RPC适合内部微服务调用,HTTP适合开放接口/浏览器访问;
- 核心价值:让开发者聚焦业务逻辑,而非分布式底层细节。
简单记:RPC框架是分布式系统的「远程调用管家」,帮你搞定跨机器调用的所有脏活累活。
下面用一张图来说下其执行流程

-
消费者(客户端)发起调用
- 消费者 :业务代码所在的客户端,它只需要调用
orderService.order(...)方法,完全感知不到这是一个远程调用。 - 代理对象 orderService:这是RPC框架生成的动态代理,它会拦截这个调用请求,把本地调用转换成RPC框架能处理的远程请求。
- 消费者 :业务代码所在的客户端,它只需要调用
-
RPC框架客户端处理
- 请求客户端(HTTP/其他):代理对象将调用信息(接口名、方法名、参数)交给请求客户端,由它负责网络通信。
- 序列化/反序列化:在发送请求前,请求客户端会把调用信息序列化为二进制字节流(比如用JSON、Protobuf),以便在网络中高效传输。
- 网络发送:请求客户端通过HTTP或其他协议(如TCP),将序列化后的请求发送给服务端的Web服务器。
-
RPC框架服务端处理
- Web服务器:监听并接收客户端的请求,然后将请求转发给「请求处理器」。
- 请求处理器:接收请求后,先对字节流进行反序列化,还原出原始的调用信息(接口名、方法名、参数)。
- 本地服务注册器 :根据接口名
orderService,在注册器中找到对应的本地业务实现类(服务提供者的真实代码)。
-
服务提供者执行方法
- 提供者(orderService接口/order方法) :本地服务注册器定位到实现类后,调用其
order方法执行真实的业务逻辑(比如创建订单、查询数据)。
- 提供者(orderService接口/order方法) :本地服务注册器定位到实现类后,调用其
代码后续会上传到github,这里先记录下项目结构与模块划分,方便大家理解。
项目结构与模块划分
这个简易RPC框架项目分为四个主要模块,各模块职责明确,分工协作:
1. 核心框架模块(feng-rpc-easy)
这是RPC框架的核心实现,包含以下子模块:
1.1 数据模型层(model)
- RpcRequest.java:RPC请求数据结构,包含服务名、方法名、参数类型和参数值
- RpcResponse.java:RPC响应数据结构,包含响应数据、响应消息、异常信息和数据类型
1.2 序列化层(serializer)
- Serializer.java:序列化接口,定义了序列化和反序列化方法
- JDKSerializer.java:JDK序列化实现,使用ObjectOutputStream和ObjectInputStream进行序列化和反序列化
1.3 服务注册层(registry)
- LocalRegistry.java:本地服务注册表,使用ConcurrentHashMap存储服务名和对应的实现类
1.4 代理层(proxy)
- ServiceProxy.java:动态代理实现,实现InvocationHandler接口,负责将方法调用转换为RPC请求
- ServiceProxyFactory.java:代理工厂,用于创建服务代理实例
1.5 服务器层(server)
- HttpServer.java:HTTP服务器接口,定义了启动服务器的方法
- VertxHttpServer.java:使用Vert.x实现的HTTP服务器
- HttpServerHandler.java:HTTP请求处理器,负责处理RPC请求的核心逻辑
2. 公共模块(example-common)
定义了客户端和服务端共享的数据模型和服务接口:
- User.java:用户数据模型
- UserService.java:用户服务接口
3. 服务提供者模块(example-provider)
实现了服务接口并提供服务:
- UserServiceImpl.java:UserService接口的实现类
- EasyProviderExample.java:服务提供者启动类,负责注册服务和启动服务器
4. 服务消费者模块(example-consumer)
调用远程服务:
- UserServiceProxy.java:静态代理实现(已被动态代理替代)
- EasyConsumerExample.java:服务消费者启动类,负责获取服务代理并调用服务
核心实现原理
1. 服务注册与发现
服务提供者在启动时,通过LocalRegistry.register()方法将服务接口名和实现类注册到本地注册表中。服务消费者通过服务接口名从注册表中获取服务实现类。
2. 序列化与反序列化
使用JDK自带的序列化机制,将Java对象转换为字节数组进行网络传输,接收方再将字节数组转换回Java对象。
3. 动态代理
客户端使用JDK动态代理生成服务代理实例,当调用代理方法时,代理会将方法调用转换为RpcRequest对象,然后通过HTTP发送到服务端。
4. HTTP通信
使用Vert.x实现的HTTP服务器处理客户端请求,服务端接收到请求后,反序列化得到RpcRequest对象,然后通过反射调用对应的服务方法,最后将结果序列化为RpcResponse对象返回给客户端。
工作流程
服务提供者
- 实现公共接口(如UserServiceImpl实现UserService接口)
- 将服务注册到本地注册表:
LocalRegistry.register(UserService.class.getName(), UserServiceImpl.class) - 启动HTTP服务器:
vertxHttpServer.doStart(8080)
服务消费者
- 获取服务代理:
UserService userService = ServiceProxyFactory.getProxy(UserService.class) - 调用代理方法:
User newUser = userService.getUser(user) - 代理将方法调用转换为RpcRequest对象
- 序列化RpcRequest并发送HTTP请求:
HttpRequest.post("http://localhost:8080").body(bodyBytes).execute() - 接收响应并反序列化为RpcResponse对象
- 返回结果给调用者:
return (User) rpcResponse.getData()
服务端处理
- 接收HTTP请求:
HttpServerHandler.handle()方法 - 反序列化请求体得到RpcRequest对象
- 从本地注册表获取服务实现类:
LocalRegistry.get(serviceName) - 通过反射调用服务方法:
method.invoke(clazz.newInstance(), rpcRequest.getArgs()) - 创建RpcResponse对象并设置结果
- 序列化RpcResponse并返回响应:
doResponse(request, rpcResponse, serializer)
实现注意事项
1. 服务名一致性
服务注册和调用必须使用相同的服务名,否则会导致服务查找失败。在这个项目中,服务名使用接口的全限定名(如UserService.class.getName())。
2. 序列化方式统一
客户端和服务端必须使用相同的序列化方式,否则会导致反序列化失败。这个项目中使用JDK序列化,要求所有传输的对象都实现Serializable接口。
3. 异常处理
目前的异常处理比较简单,只是打印异常信息,没有完善的异常传递机制。在实际应用中,需要将服务端的异常正确传递给客户端。
4. 服务发现机制
当前使用的是本地注册表,只适用于单机环境。在分布式环境中,需要使用远程注册中心(如ZooKeeper、Nacos等)。
5. 负载均衡
当前没有实现负载均衡机制,客户端只能调用固定地址的服务。在实际应用中,需要实现负载均衡来分发请求。
6. 配置化
端口、地址等信息目前是硬编码的,在实际应用中,应该通过配置文件或配置中心进行配置。
7. 线程安全
本地注册表使用ConcurrentHashMap保证线程安全,适合多线程环境下使用。
8. 资源释放
在序列化和反序列化过程中,需要注意关闭流资源,避免资源泄漏。