RPC
RPC 全称是 Remote Procedure Call,远程过程调用。
客户端代码
↓ 调用接口方法(看起来完全是本地调用)
Client Stub(客户端代理)
↓ 把“方法名 + 参数”打包(序列化)
网络传输(TCP / HTTP2 等)
↓
Server Stub / Skeleton(服务端骨架)
↓ 解包(反序列化)→ 找到真正的实现类 → 执行方法
真正的业务实现类
↓ 返回结果
Server Stub 把结果打包发回
↓
Client Stub 解包结果,返回给调用方RPC比HTTP更适合内部服务调用
RPC 比 REST 风格的 HTTP 接口更适合内部服务调用,主要有四个原因。第一,RPC 通常使用 Protobuf 等二进制序列化,数据体积更小,序列化速度更快,更适合高频低延迟调用。第二,RPC 使用接口或 IDL 定义强类型契约,很多参数不匹配问题可以在编译阶段发现。第三,RPC 框架通常集成服务注册发现、负载均衡、超时、重试和熔断等服务治理能力。第四,RPC 会把远程调用封装成类似本地方法调用,降低内部服务开发成本。不过 RPC 调试和跨平台开放性不如 REST,所以一般对外使用 HTTP REST,对内使用 RPC。
根本原因是RPC针对服务间高频、低延迟、强契约调用做了专门优化。
使用REST时,调用方需要关心URL、HTTP方法、Header、JSON序列化、状态码、响应体解析,但是使用RPC时,调用方式更接近普通Java方法,例如
User user = userService.getUserById(1001L);RPC性能通常更高
REST 接口通常使用JSON:{ "userId": 1001 "username": "zhangsan" "status": "ACTIVE" }JSON存在几个特点:
- 字段名会重复传输
- 本质时文本
- 需要字符串解析
- 数据类型表达不严格
RPC使用Protobuf、Hessian、Thrift等二进制序列化格式
例如:message User{ int64 user_id = 1; string username = 2; string status = 3; }传输时主要使用字段编号1、2、3,不需要反复传输完整字段名。
所以通常表现为:- 数据包更小
- 序列化速度更快
- 反序列化速度更快
- 网络带宽占用更低
- 高频调用下延迟更小
RPC强契约接口
REST接口经常依赖接口文档约定请求: { "userId": 1001, "amount": 99 }调用方可能遇到这些问题:
- userId到底是数字还是字符串
- amount是否允许为空
- status有哪些可选值
- 字段被改名后,调用方什么时候可以发现
很多问题要到运行时才暴露
RPC通常使用Java接口或IDL文件定义契约
例如gRPCservice OrderService { rpc CreateOrder(CreateOrderRequest) return (CreateOrderReply); } message CreateOrderRequest { int64 user_id = 1; double amount = 2; }根据.proto文件生成Java代码后,调用方只能按照规定的类型进行调用:
CreateOrderRequest request = CreateOrderRequest.newBuilder() .serUserId(1001L) .setAmount(99.9) .build();如果错误传入.setUserId("1001"),程序编译阶段就会报错。
REST: 接口字段不匹配 → 可能运行时才发现 RPC: 参数类型不匹配 → 编译阶段就能发现- RPC 框架集成了微服务治理能力
- RPC 更容易支持长连接和多路复用
传统 HTTP/1.1 场景下,连接管理效率相对有限。
而许多 RPC 框架会使用:
TCP 长连接
连接池
HTTP/2
多路复用
异步请求
流式通信
例如 gRPC 基于 HTTP/2,可以在同一个连接上并发传输多个请求:
同一条连接
├── 请求 A
├── 请求 B
├── 请求 C
└── 请求 D总的来说:REST 优先解决开放性和通用性,RPC 优先解决内部服务调用的性能、契约和治理问题。
标签:无