Appearance
RPC 与 REST 详解:通信范式选型指南
RPC 和 REST 是分布式系统中最主流的两种服务通信范式。它们不是非黑即白的选择,而是适用不同场景的两套设计哲学。理解它们的本质差异,才能在微服务架构中做出正确的选型。
一、什么是 RPC?
1.1 核心思想
RPC(Remote Procedure Call,远程过程调用)的核心思想是:让远程服务调用像调用本地方法一样简单。开发者不需要关心网络通信、序列化、路由等底层细节,只需要调用一个"看起来像本地"的方法。
┌──────────────────────────────────────────────────────────────────┐
│ RPC 调用过程 │
│ │
│ 调用方(Consumer) 被调方(Provider) │
│ │
│ orderService.createOrder(dto) public Order createOrder │
│ │ (OrderDTO dto) { │
│ │ │ │
│ ▼ │ │
│ ┌──────────┐ │ │
│ │ Stub 代理 │ ← 看起来像本地方法 │ │
│ │ (动态代理)│ │ │
│ └────┬─────┘ │ │
│ │ │ │
│ │ ① 序列化(DTO → 二进制) │ │
│ ▼ │ │
│ ┌──────────┐ 网络传输 ┌──────────┐ │
│ │ 网络层 │ ◄─────────────────────► │ 网络层 │ │
│ │ (TCP) │ │ (TCP) │ │
│ └──────────┘ └────┬─────┘ │
│ │ │
│ │ ② 反序列化 │
│ ▼ │
│ ┌──────────┐ │
│ │ 实际执行 │ │
│ │ 方法体 │ │
│ └────┬─────┘ │
│ │ │
│ │ ③ 序列化返回值 │
│ ▼ │
│ ┌──────────┐ 网络传输 ┌──────────┐ │
│ │ 收到响应 │ ◄───────────────────── │ 返回结果 │ │
│ └────┬─────┘ └──────────┘ │
│ │ │
│ │ ④ 反序列化(二进制 → Order) │
│ ▼ │
│ Order result = ... ← 调用方拿到结果,就像本地方法返回一样 │
└──────────────────────────────────────────────────────────────────┘1.2 RPC 框架代表
| 框架 | 语言 | 协议 | 序列化 | 生态 |
|---|---|---|---|---|
| Dubbo | Java | 自定义 TCP(dubbo://) | Hessian2 / Kryo / Protobuf | Spring Cloud Alibaba |
| gRPC | 多语言 | HTTP/2 | Protobuf | Google 出品,CNCF |
| Thrift | 多语言 | 自定义 TCP | Thrift Binary | Facebook 出品 |
| Motan | Java | 自定义 TCP | Hessian2 / FastJSON | 微博出品 |
| SOFARPC | Java | 自定义 TCP | Hessian2 / Protobuf | 蚂蚁出品 |
1.3 Dubbo 代码示例
java
// ===== 接口定义(共享 jar 包) =====
public interface OrderService {
Order createOrder(OrderDTO dto);
}
// ===== 服务提供者 =====
@DubboService
public class OrderServiceImpl implements OrderService {
@Override
public Order createOrder(OrderDTO dto) {
// 业务逻辑
return new Order(dto.getId(), "创建成功");
}
}
// ===== 服务消费者 =====
@Component
public class OrderController {
@DubboReference // 注入远程代理,像用本地 Bean 一样
private OrderService orderService;
public void create() {
OrderDTO dto = new OrderDTO(1L, "iPhone");
Order order = orderService.createOrder(dto); // 像调用本地方法
}
}1.4 gRPC 代码示例
protobuf
// order.proto — 接口定义文件
service OrderService {
rpc CreateOrder (OrderRequest) returns (OrderResponse);
}
message OrderRequest {
int64 id = 1;
string product = 2;
int32 quantity = 3;
}
message OrderResponse {
int64 id = 1;
string status = 2;
}java
// 服务端
@GrpcService
public class OrderServiceImpl extends OrderServiceGrpc.OrderServiceImplBase {
@Override
public void createOrder(OrderRequest req, StreamObserver<OrderResponse> resp) {
OrderResponse response = OrderResponse.newBuilder()
.setId(req.getId())
.setStatus("创建成功")
.build();
resp.onNext(response);
resp.onCompleted();
}
}
// 客户端
OrderServiceGrpc.OrderServiceBlockingStub stub = OrderServiceGrpc.newBlockingStub(channel);
OrderResponse response = stub.createOrder(OrderRequest.newBuilder()
.setId(1L)
.setProduct("iPhone")
.setQuantity(2)
.build());二、什么是 REST?
2.1 核心思想
REST(Representational State Transfer,表述性状态转移)的核心思想是:把一切抽象为资源(Resource),通过标准的 HTTP 方法操作资源的状态。REST 不是协议,而是一种架构风格。
┌──────────────────────────────────────────────────────────────────┐
│ REST 调用过程 │
│ │
│ 调用方(Consumer) 被调方(Provider) │
│ │
│ POST /api/order/create @PostMapping("/api/order │
│ Content-Type: application/json /create") │
│ Body: {"id":1,"product":"iPhone"} public Order createOrder │
│ │ (@RequestBody OrderDTO dto) │
│ │ │ │
│ ▼ │ │
│ ┌──────────┐ │ │
│ │ HTTP 客户端│ ← 构造 HTTP 请求 │ │
│ │ (HttpClient│ │ │
│ │ /OkHttp) │ │ │
│ └────┬─────┘ │ │
│ │ │ │
│ │ ① JSON 序列化(DTO → JSON 字符串) │ │
│ ▼ │ │
│ ┌──────────┐ HTTP/1.1 传输 ┌──────────┐ │
│ │ 网络层 │ ◄─────────────────────► │ 网络层 │ │
│ │ (TCP) │ HTTP Method + URL │ (TCP) │ │
│ │ │ + Headers + Body │ │ │
│ └──────────┘ └────┬─────┘ │
│ │ │
│ │ ② JSON 反序列化 │
│ ▼ │
│ ┌──────────┐ │
│ │ Spring MVC│ │
│ │ 路由匹配 │ │
│ │ 参数绑定 │ │
│ │ 执行业务 │ │
│ └────┬─────┘ │
│ │ │
│ │ ③ 序列化返回值 │
│ ▼ │
│ ┌──────────┐ HTTP 响应 ┌──────────┐ │
│ │ 收到响应 │ ◄───────────────────── │ 返回 JSON │ │
│ │ Status 200│ │ + Status │ │
│ └────┬─────┘ └──────────┘ │
│ │ │
│ │ ④ JSON 反序列化(JSON → Order) │
│ ▼ │
│ Order order = ... ← 调用方拿到结果 │
└──────────────────────────────────────────────────────────────────┘2.2 REST 的核心约束
| 约束 | 说明 | 示例 |
|---|---|---|
| 资源标识 | 每个资源有唯一的 URI | /api/orders/123 |
| 统一接口 | 使用标准 HTTP 方法 | GET/POST/PUT/DELETE |
| 无状态 | 每个请求包含所有必要信息 | 不依赖服务端 Session |
| 表述 | 资源可以有多种表述形式 | JSON / XML / HTML |
| HATEOAS | 超媒体驱动(可选) | 响应中包含下一步链接 |
2.3 REST 风格示例
java
// 接口定义 — 遵循 RESTful 风格
@RestController
@RequestMapping("/api/orders")
public class OrderController {
// GET /api/orders/123 → 查询订单
@GetMapping("/{id}")
public Order getOrder(@PathVariable Long id) { ... }
// POST /api/orders → 创建订单
@PostMapping
public Order createOrder(@RequestBody OrderDTO dto) { ... }
// PUT /api/orders/123 → 更新订单
@PutMapping("/{id}")
public Order updateOrder(@PathVariable Long id, @RequestBody OrderDTO dto) { ... }
// DELETE /api/orders/123 → 删除订单
@DeleteMapping("/{id}")
public void deleteOrder(@PathVariable Long id) { ... }
// GET /api/orders?page=1&size=20 → 分页查询
@GetMapping
public Page<Order> listOrders(@RequestParam int page, @RequestParam int size) { ... }
}三、RPC vs REST 核心对比
3.1 设计哲学
RPC:面向动作(Action-Oriented)
"我要调用你的 createOrder 方法"
关注点:做什么操作?传什么参数?
接口名称就是操作名:createOrder()、cancelOrder()、queryOrder()
REST:面向资源(Resource-Oriented)
"我要操作 /api/orders/123 这个资源"
关注点:操作哪个资源?用什么 HTTP 方法?
URL 代表资源,HTTP 方法代表操作:GET /orders/123、POST /orders3.2 全方位对比
| 维度 | RPC | REST |
|---|---|---|
| 通信协议 | 自定义 TCP 协议(Dubbo)/ HTTP/2(gRPC) | HTTP/1.1 或 HTTP/2 |
| 序列化格式 | 二进制(Protobuf、Hessian2、Kryo) | 文本(JSON、XML) |
| 接口定义 | 强契约:共享接口 jar / .proto 文件 | 弱契约:OpenAPI 文档(可选) |
| 调用方式 | orderService.createOrder(dto) 像本地方法 | POST /api/orders HTTP 请求 |
| 耦合度 | 强耦合(接口 jar 依赖,编译期绑定) | 弱耦合(仅 HTTP 端点约定,运行时绑定) |
| 跨语言 | 弱(Dubbo 专注 Java,gRPC 可跨语言) | 强(任何语言都能发 HTTP 请求) |
| 性能 | 高(二进制序列化、长连接、多路复用) | 中(JSON 文本序列化开销大,HTTP/1.1 队头阻塞) |
| 网络开销 | 小(二进制协议,头部极小) | 大(HTTP 头部 + JSON 键名重复) |
| 调试 | 难(二进制不可读,需专用工具) | 易(curl/Postman/浏览器直接查看) |
| 缓存 | 难(协议层面不支持) | 易(HTTP 缓存头、CDN、反向代理) |
| 负载均衡 | 需注册中心(Dubbo 多层 LB) | 通用(L4/L7 负载均衡器均可) |
| 流式传输 | gRPC 原生支持双向流 | SSE / WebSocket(需额外处理) |
| 超时控制 | 框架内置(可精细控制) | HTTP 超时(依赖客户端实现) |
| 服务治理 | 框架内置(Dubbo 有完整治理能力) | 依赖外部组件(Gateway、Sentinel) |
| 学习成本 | 高(需理解 RPC 框架原理) | 低(HTTP 是基础技能) |
| 代表框架 | Dubbo、gRPC、Thrift | Spring MVC、OpenFeign、WebClient |
3.3 性能对比(实测数据参考)
场景:单次请求,Payload 1KB
RPC(Dubbo + Hessian2):
TCP 包大小:~1.1KB
TPS:~50,000/s(长连接)
平均延迟:~2ms
REST(HTTP + JSON):
HTTP 包大小:~1.5KB(HTTP 头 + JSON 键名)
TPS:~20,000/s(HTTP/1.1)
平均延迟:~5ms
RPC(gRPC + Protobuf + HTTP/2):
HTTP/2 帧大小:~1.05KB
TPS:~45,000/s(HTTP/2 多路复用)
平均延迟:~2.5ms
结论:RPC 性能比 REST 高 2-10 倍,gRPC 最接近 RPC 性能。
但 REST 性能对大多数业务场景足够了(20K TPS 远超业务需求)。3.4 序列化效率对比
同一个对象:OrderDTO { id: 12345, product: "iPhone 15 Pro", quantity: 2, price: 8999.00 }
JSON(REST 默认):
{
"id": 12345,
"product": "iPhone 15 Pro",
"quantity": 2,
"price": 8999.00
}
→ 87 字节
Protobuf(gRPC 默认):
08 39 30 12 0D 69 50 68 6F 6E 65 20 31 35 20 50 72 6F 18 02 ...
→ 35 字节
Hessian2(Dubbo 默认):
43 4A 01 04 6E 61 6D 65 ...
→ 40 字节
结论:二进制序列化体积约为 JSON 的 40-50%,解析速度更是快 3-5 倍。四、RPC 的深层优势
4.1 接口契约(强类型安全)
java
// RPC:编译期就发现接口不匹配
// 提供者改了方法签名 → 消费者编译报错 → 立即发现
@DubboService
public class OrderServiceImpl implements OrderService {
@Override
public Order createOrder(OrderDTO dto) { ... } // 改了方法名
}
// 消费者:
@DubboReference
private OrderService orderService;
orderService.createOrder(dto); // ← 编译就报错,不会上线才发现
// REST:运行时才发现路径不匹配
// 提供者改了路径 → 消费者 404 → 上线后才发现
@PostMapping("/api/order/new") // 原来叫 /api/order/create
public Order createOrder(@RequestBody OrderDTO dto) { ... }4.2 长连接与多路复用
REST(HTTP/1.1):
请求1 ────→ 等待响应1 ────→ 收到响应1 ────→ 请求2 ────→ 等待响应2 ────→ 收到响应2
连接数 = 并发数(每个请求一个连接或排队)
RPC(Dubbo 长连接):
TCP 连接 ──── 请求1 ────→
├── 请求2 ────→ ← 一个连接承载多个请求
├── 请求3 ────→
└── 请求4 ────→
连接数 = 1(无论多少并发)
gRPC(HTTP/2 多路复用):
TCP 连接 ──── Stream1 ────→
├── Stream2 ────→ ← HTTP/2 多路复用,一个连接多个 Stream
├── Stream3 ────→
└── Stream4 ────→
连接数 = 1,且每个 Stream 独立,不存在队头阻塞4.3 服务治理能力
yaml
# Dubbo 内置服务治理配置(无需额外组件)
dubbo:
provider:
timeout: 3000 # 超时
retries: 2 # 重试
loadbalance: roundrobin # 负载均衡
cluster: failover # 集群容错
weight: 100 # 权重
actives: 200 # 并发限制
version: 1.0.0 # 版本(灰度发布)
group: dev # 分组REST 实现这些需要 Gateway + Sentinel + LoadBalancer 等多个组件协同。
五、REST 的深层优势
5.1 跨语言无障碍
REST:
Java: restTemplate.postForObject(url, body, Order.class)
Go: http.Post(url, "application/json", body)
Python: requests.post(url, json=body)
Node: fetch(url, { method: 'POST', body: JSON.stringify(data) })
Shell: curl -X POST url -d '{"id":1}'
任何语言、任何工具,只要能发 HTTP 请求就能调用。
RPC(Dubbo):
Java: orderService.createOrder(dto) ✅ 原生支持
Go: dubbo-go 客户端 ⚠️ 需要专门的 Go SDK
Python: dubbo-python 客户端 ⚠️ 需要专门的 Python SDK
Node: 无官方支持 ❌ 无法调用5.2 生态兼容性
REST 天然穿透
─────────────→
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ 浏览器 │→│ CDN │→│ 防火墙 │→│ Nginx │→│ 服务 │
└──────┘ └──────┘ └──────┘ └──────┘ └──────┘
✅ ✅ ✅ ✅ ✅
RPC 可能被拦截
─────────────→
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ 客户端 │→│ CDN │→│ 防火墙 │→│ Nginx │→│ 服务 │
└──────┘ └──────┘ └──────┘ └──────┘ └──────┘
✅ ❌ ❌ ❌ ✅
看不懂 可能拦截 需要TCP代理
自定义协议 自定义协议5.3 调试和可观测性
bash
# REST:直接 curl 验证
curl -X POST http://order-service/api/orders \
-H "Content-Type: application/json" \
-d '{"product":"iPhone","quantity":2}'
# 返回:{"id":12345,"status":"创建成功"} ← 一目了然
# RPC(Dubbo):需要 telnet 或专用工具
telnet localhost 20880
dubbo> invoke com.example.OrderService.createOrder({"product":"iPhone","quantity":2})
# 返回:二进制数据,常人不可读
# REST:抓包后可读
HTTP/1.1 200 OK
Content-Type: application/json
{"id":12345,"status":"创建成功"} ← 任何抓包工具都能直接看到
# RPC:抓包后不可读
00 00 00 1A 0A 0E 63 6F 6D 2E 65 78 61 6D 70 6C ... ← 需要专门解码5.5 REST 高并发与高可用
REST 完全可以做到高并发和高可用。 很多人把"REST 比 RPC 慢"误解为"REST 不能高并发",这是两个完全不同的概念。REST 慢的是单次请求延迟,不是并发能力。
5.5.1 高并发:REST 如何做到?
误区:REST = 阻塞 IO = 低并发
真相:REST 是一种通信范式,与 IO 模型无关
REST 的并发能力取决于底层实现:
┌──────────────────────────────────────────────────────────────────┐
│ Servlet 容器(Tomcat) │
│ 线程模型:一个请求一个线程 │
│ 最大连接数:200(默认) │
│ 并发能力:中等(可调大线程池) │
├──────────────────────────────────────────────────────────────────┤
│ Reactor Netty(WebClient) │
│ 线程模型:事件驱动,少量线程处理大量请求 │
│ 并发能力:数万连接/实例 │
│ 代表:Spring WebFlux、WebClient │
├──────────────────────────────────────────────────────────────────┤
│ Undertow │
│ 线程模型:NIO + 工作线程池 │
│ 并发能力:高(Tomcat 的 1.5-2 倍) │
└──────────────────────────────────────────────────────────────────┘REST 高并发的关键手段:
| 手段 | 说明 | 效果 |
|---|---|---|
| 异步非阻塞 | WebFlux + WebClient,事件驱动 | 1 台机器数万并发连接 |
| HTTP/2 多路复用 | 一个 TCP 连接承载多个请求 | 消除 HTTP/1.1 队头阻塞 |
| 连接池 | 复用 HTTP 连接,避免频繁握手 | 减少 90% 连接开销 |
| 水平扩展 | 无状态服务,加机器即可 | 理论上无限扩展 |
| CDN + 缓存 | 静态资源走 CDN,热点数据走缓存 | 大幅减少后端请求 |
| 异步处理 | 请求先受理,后台异步处理 | 削峰填谷 |
高并发 REST 架构:
用户请求(100K QPS)
│
▼
┌──────────┐
│ CDN │ ← 静态资源直接返回,不回源
└────┬─────┘
│ 动态请求
▼
┌──────────┐
│ Nginx │ ← 反向代理 + 负载均衡,10W+ 连接无压力
└────┬─────┘
│
▼
┌──────────────────────────────────────────┐
│ Gateway 集群 │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ 实例 1 │ │ 实例 2 │ │ 实例 3 │ ... │ ← 水平扩展
│ └────────┘ └────────┘ └────────┘ │
└──────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ 业务服务集群(无状态) │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ 实例 1 │ │ 实例 2 │ │ 实例 3 │ ... │
│ │ WebFlux│ │ WebFlux│ │ WebFlux│ │ ← 异步非阻塞
│ └────────┘ └────────┘ └────────┘ │
└──────────────────────────────────────────┘
│
▼
┌──────────┐ ┌──────────┐
│ Redis │ │ MQ │ ← 异步削峰
│ 缓存 │ │ 队列 │
└──────────┘ └──────────┘5.5.2 高可用:REST 如何做到?
REST 天然支持高可用的核心原因:无状态。
REST 的无状态特性:
每个请求独立,包含所有必要信息(Token、参数)
任何实例都能处理任何请求
实例挂了 → 流量自动切到其他实例 → 用户无感知
RPC 的挑战:
Dubbo 长连接:客户端和特定实例建立了 TCP 连接
实例挂了 → 连接断开 → 需要重新建立连接 → 短暂不可用
虽然 Dubbo 有自动重连,但恢复过程有短暂抖动REST 高可用架构:
┌─────────────────────────────────────┐
│ Nginx / Gateway 集群 │
│ 健康检查 + 自动剔除故障节点 │
└──────────┬──────────┬───────────────┘
│ │
┌────────────────┼──────────┼──────────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 实例 1 │ │ 实例 2 │ │ 实例 3 │ │ 实例 4 │
│ (正常) │ │ (正常) │ │ (故障) │ │ (正常) │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
▲ ▲ ✗ ▲
│ │ │ │
└───────────────┴───────────────┴────────────────┘
负载均衡自动跳过故障实例
REST 高可用三板斧:
① 无状态服务 → 水平扩展,任意实例可替换
② 负载均衡 → 自动剔除故障节点,流量分发到健康实例
③ 熔断降级 → 下游故障时快速失败,返回兜底数据5.5.3 REST 的真实瓶颈在哪?
| 瓶颈 | 问题 | 解决方案 |
|---|---|---|
| JSON 序列化开销 | 文本序列化 CPU 消耗大 | 换 Protobuf / JSONB / 压缩 |
| HTTP/1.1 队头阻塞 | 单连接请求串行排队 | 升级 HTTP/2,多路复用 |
| HTTP 头部冗余 | 每次请求都带完整 Header | HTTP/2 HPACK 头部压缩 |
| TCP 握手开销 | 每次新建连接 3 次握手 | 连接池复用 + Keep-Alive |
| 数据库瓶颈 | 最终瓶颈都在 DB | 读写分离、分库分表、缓存 |
核心结论:REST 的瓶颈不在 REST 本身,而在底层 IO 模型和序列化格式。 用 WebFlux + HTTP/2 + Protobuf,REST 可以接近 RPC 的性能。
5.5.4 REST vs RPC 高并发场景对比
| 场景 | REST 方案 | RPC 方案 | 结论 |
|---|---|---|---|
| 秒杀(瞬时 10W QPS) | Nginx 限流 + 异步队列 + 水平扩展 | Dubbo 长连接 + 并行调用 | 都能扛,REST 运维成本更低 |
| 服务间高频调用(1W+ QPS) | WebClient + HTTP/2 + 连接池 | Dubbo 长连接 | 性能差距 2-3 倍,RPC 更优 |
| 流式数据(日志/监控) | SSE / WebSocket | gRPC 双向流 | gRPC 更优雅 |
| 对外 API(第三方调用) | REST(唯一选择) | 不适用 | REST 无替代 |
| 微服务集群(100+ 服务) | Gateway + 服务发现 | Dubbo 注册中心 | 都能胜任,取决于技术栈 |
一句话:REST 完全可以高并发高可用,只是单次请求延迟比 RPC 高。对于绝大多数业务场景(QPS < 1W),REST 的性能完全够用,而且运维成本更低。
六、如何选型?
6.1 决策树
┌─ 是否需要跨语言调用? ──┐
│ 是 │ 否
▼ ▼
┌─ 需要高性能? ──┐ ┌─ 需要高性能? ──┐
│ 是 │ 否 │ 是 │ 否
▼ ▼ ▼ ▼
gRPC REST Dubbo / gRPC REST
(HTTP/2+Protobuf) (纯 Java 选 Dubbo,
跨语言选 gRPC)6.2 场景化推荐
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 对外开放 API(给第三方调用) | REST | 跨语言、调试友好、文档规范 |
| 内部微服务间调用(纯 Java) | Dubbo RPC | 高性能、内置治理、编译期安全 |
| 内部微服务间调用(多语言) | gRPC | HTTP/2 + Protobuf,跨语言高性能 |
| 高并发低延迟场景(<1ms 要求) | Dubbo RPC | 二进制协议、长连接、零拷贝 |
| 流式数据传输(日志、监控) | gRPC | 原生双向流支持 |
| 前端 ← → 后端 | REST | 浏览器原生支持 HTTP |
| 快速迭代项目 | REST | 松耦合、无需共享接口 jar |
| 已有 Spring Cloud 体系 | REST(OpenFeign) | 生态一致,开箱即用 |
| 已有 Spring Cloud Alibaba | Dubbo RPC | 与 Nacos 天然集成 |
| 网关层 → 服务层 | REST | 网关(Gateway)天然支持 HTTP |
| 服务层 → 服务层(高频调用) | Dubbo RPC | 绕过网关,直连高性能 |
6.3 混合使用方案(推荐)
┌─────────────────────────────────────────┐
│ 最佳实践:混用 │
└─────────────────────────────────────────┘
外部请求(浏览器 / App / 第三方)
│
│ REST(HTTP + JSON)
▼
┌──────────────┐
│ Gateway │ ← REST 统一入口(鉴权、限流、路由)
└──────┬───────┘
│
│ REST
▼
┌──────────────┐
│ order-service │
│ │
│ ┌──────────┐│
│ │ REST API ││ ← 对外暴露 REST 接口
│ └──────────┘│
│ │
│ ┌──────────┐│
│ │Dubbo Ref ││ ← 调用 inventory-service 用 Dubbo(高性能)
│ └──────────┘│
│ │
│ ┌──────────┐│
│ │OpenFeign ││ ← 调用 payment-service 用 REST(跨团队)
│ └──────────┘│
└──────────────┘
原则:
- 对外 API 统一用 REST
- 内部高频调用用 RPC(Dubbo/gRPC)
- 跨团队调用用 REST(松耦合)
- 同一团队内部用 RPC(高性能 + 编译期安全)6.4 选型 Checklist
| 条件 | 选 RPC | 选 REST |
|---|---|---|
| 需要跨语言调用 | ❌(选 gRPC) | ✅ |
| 追求极致性能(<1ms) | ✅ | ❌ |
| 需要浏览器直接调用 | ❌ | ✅ |
| 需要过 CDN 缓存 | ❌ | ✅ |
| 需要流式传输 | ✅(gRPC) | ⚠️(SSE/WebSocket) |
| 需要编译期类型安全 | ✅ | ❌ |
| 需要 curl 调试 | ❌ | ✅ |
| 团队技术栈统一(纯 Java) | ✅ | ✅ |
| 需要对外开放 API | ❌ | ✅ |
| 需要服务治理内置 | ✅(Dubbo) | ❌(需外部组件) |
| 快速原型开发 | ❌ | ✅ |
七、面试高频考点
Q1:RPC 和 REST 的本质区别是什么?
RPC 面向动作(Action-Oriented),目标是"像调用本地方法一样调用远程方法",接口名就是操作名。REST 面向资源(Resource-Oriented),目标是"操作 URL 标识的资源",HTTP 方法表示操作类型。RPC 追求性能和编译期安全,REST 追求松耦合和跨语言。
Q2:为什么 Dubbo 比 REST 性能高?
- 二进制序列化(Hessian2/Protobuf)比 JSON 快 3-5 倍
- 长连接复用,避免 HTTP 握手开销
- 自定义协议头部极小,HTTP 头部有冗余
- 零拷贝、NIO 多路复用等底层优化
Q3:gRPC 算是 RPC 还是 HTTP?
gRPC 是 RPC 框架,但底层使用 HTTP/2 作为传输协议。它继承了 RPC 的强契约(.proto 文件)和高性能(Protobuf 序列化),同时利用了 HTTP/2 的多路复用、双向流等现代特性。可以理解为"HTTP/2 上的 RPC"。
Q4:REST 能不能实现高性能?
可以,但需要额外工作。REST + HTTP/2 + JSONB(二进制 JSON)+ 长连接池可以接近 RPC 性能。但大部分团队不会做这些优化,这就导致了"REST 比 RPC 慢"的普遍认知。
Q5:Spring Cloud 项目中怎么混用 RPC 和 REST?
对外 API 用 REST(Gateway + Spring MVC),内部调用用 Dubbo(Spring Cloud Alibaba),两者通过 Nacos 统一注册发现。Gateway 路由 REST 请求,Dubbo 直连处理内部调用,各取所长。
Q6:微服务架构中,接口变更怎么处理?
RPC:共享接口 jar 版本管理,提供者保证向后兼容,消费者按需升级。REST:API 版本化(/api/v1/orders、/api/v2/orders),或请求头版本号。REST 的版本管理更灵活,不需要消费者重新编译。
