REST API 已经能完成绝大多数系统之间的通信,但它主要解决的是“以资源为中心的通用 HTTP 接口”;gRPC 等 RPC 框架解决的是“以服务调用为中心的高性能、强契约、自动生成代码的内部通信”。
它们不是谁取代谁,而是面向的场景不同。
一、先区分三个概念
1. HTTP 是传输协议
HTTP 负责:
- 请求怎么发送
- 响应怎么返回
- Header、状态码如何表达
- 连接如何建立和复用
REST 和 gRPC 都可以建立在 HTTP 之上。
例如:
- REST 通常使用 HTTP/1.1 或 HTTP/2
- gRPC 通常使用 HTTP/2
- gRPC 并不是“不使用 HTTP”
2. REST 是接口设计风格
REST 倾向于把业务抽象成资源:
GET /users/123
POST /orders
PATCH /orders/456
DELETE /sessions/789
典型表达是:
{
"id": 123,
"name": "Alice"
}
核心思想是:
客户端操作资源,而不是直接调用服务端方法。
3. RPC 是远程方法调用模型
RPC 更像调用一个远程函数:
UserService.GetUser(userId)
OrderService.CreateOrder(request)
BattleService.JoinRoom(request)
例如 gRPC 的定义:
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
}
message GetUserRequest {
int64 user_id = 1;
}
message GetUserResponse {
int64 user_id = 1;
string name = 2;
}
然后生成客户端代码:
const user = await userClient.getUser({
userId: 123,
});
调用方看起来就像调用本地方法。
二、REST 的优势到底是什么
REST 最大的优势不是性能,而是通用性和可理解性。
1. 对人友好
浏览器、curl、Postman 都能直接调用:
curl https://api.example.com/users/123
返回 JSON 后人可以直接看懂:
{
"id": 123,
"name": "Alice"
}
而 protobuf 二进制默认不适合人直接阅读。
2. 对外部调用者友好
假设你提供一个支付平台、SaaS 平台或者开放平台,调用者可能使用:
- JavaScript
- Python
- Java
- PHP
- Go
- Ruby
- C#
- curl
- 自动化平台
- 浏览器插件
REST 几乎没有接入门槛。
只要会发送 HTTP 请求就能使用。
3. 与 Web 生态天然兼容
REST 可以直接使用:
- 浏览器 Fetch API
- CDN
- HTTP 缓存
- API Gateway
- WAF
- 反向代理
- OAuth
- CORS
- 标准日志工具
例如:
GET /products/123
Cache-Control: public, max-age=3600
ETag: "product-123-v5"
CDN 可以直接缓存。
4. 适合资源型业务
例如:
- 用户管理
- 商品管理
- 订单 CRUD
- 内容系统
- 管理后台
- SaaS 公共 API
- 文件元数据查询
- Web 前后端通信
这些天然适合:
GET /articles
GET /articles/123
POST /articles
PATCH /articles/123
DELETE /articles/123
三、既然 REST 能调用,为什么还需要 RPC
因为在大型内部系统中,开发者关心的不只是“能不能调通”,还关心:
- 类型是否严格
- 接口是否自动生成
- 数据包是否足够小
- 延迟是否足够低
- 是否支持持续流式传输
- 是否容易做跨语言调用
- 接口升级是否安全
- 大量微服务调用是否容易维护
REST 可以实现这些,但通常需要额外组合很多工具。
gRPC 把其中很多能力统一封装好了。
四、gRPC 相比 REST 的核心优势
1. 强类型接口契约
REST 经常通过 OpenAPI 描述接口:
paths:
/users/{id}:
get:
responses:
'200':
...
但现实中经常出现:
- 文档和代码不一致
- 字段改了,文档没改
- 前端以为字段一定存在,后端实际可能返回 null
- 不同服务对同一个字段定义不同
- 数字、字符串、时间格式不统一
gRPC 使用 .proto 作为接口的唯一契约:
message User {
int64 id = 1;
string nickname = 2;
int64 created_at_ms = 3;
}
修改 proto 后生成:
- Java Server Stub
- Go Client
- TypeScript Client
- C++ Client
- Python Client
调用方和服务方共享相同类型定义。
这对于几十个甚至几百个微服务非常重要。
2. 自动生成客户端和服务端代码
REST 常见调用代码:
const response = await fetch(
`https://user-service/users/${userId}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
你还需要处理:
- URL
- 请求方法
- JSON 序列化
- 错误状态
- 类型声明
- 超时
- 重试
- Header
- 返回值校验
gRPC 调用更像:
const response = await userClient.getUser({
userId,
});
底层通信细节由生成代码和框架处理。
在服务数量较多时,这会显著降低维护成本。
3. protobuf 数据更紧凑
REST 常用 JSON:
{
"userId": 123456,
"displayName": "Alice",
"accountStatus": "ACTIVE"
}
JSON 中字段名会重复传输:
userIddisplayNameaccountStatus
protobuf 使用字段编号:
message User {
int64 user_id = 1;
string display_name = 2;
AccountStatus account_status = 3;
}
线上传输时不需要重复发送完整字段名。
因此 protobuf 通常具有:
- 数据体积更小
- 序列化更快
- 反序列化更快
- 网络带宽消耗更低
不过要注意:
对普通后台 CRUD 来说,这种性能提升经常没有决定性意义。
如果一个接口本身需要查询数据库 100ms,那么 JSON 编解码节省的零点几毫秒可能并不重要。
4. 原生支持流式通信
普通 REST 最常见的是:
一个请求 → 一个响应
gRPC 原生支持四种模式。
Unary:普通请求响应
rpc GetUser(GetUserRequest) returns (GetUserResponse);
对应:
一个请求 → 一个响应
Server Streaming:服务端流
rpc SubscribeEvents(SubscribeRequest)
returns (stream Event);
对应:
一个请求 → 服务端持续返回多个消息
适合:
- 实时日志
- 任务进度
- 行情推送
- AI 模型输出
- 服务端事件流
- 大量结果分页式传送
Client Streaming:客户端流
rpc UploadChunks(stream Chunk)
returns (UploadResult);
对应:
客户端持续发送多个消息 → 服务端最终返回一个结果
适合:
- 分块上传
- 遥测数据汇总
- 设备数据采集
- 批量数据传输
Bidirectional Streaming:双向流
rpc Connect(stream ClientMessage)
returns (stream ServerMessage);
对应:
客户端持续发送
服务端也持续发送
双方相互独立
适合:
- 实时协作
- 游戏控制通道
- 设备控制
- 双向消息代理
- 实时计算任务
- 长连接服务通信
虽然 WebSocket 也能实现双向流,但 gRPC 提供了:
- 类型定义
- 消息格式
- 流接口
- 错误模型
- 代码生成
- 截止时间和取消传播
5. HTTP/2 多路复用
HTTP/1.1 下,一个连接同时处理多个请求时存在较多限制。
HTTP/2 支持多路复用:
一个 TCP 连接
├── Stream 1:GetUser
├── Stream 3:CreateOrder
├── Stream 5:ReportMetric
└── Stream 7:SubscribeEvents
多个请求可以共用一条连接。
对于服务之间大量小请求,优势比较明显:
- 减少连接建立
- 减少连接数量
- 降低连接管理成本
- 支持长连接
- 更适合高频调用
不过现代 REST 也可以运行在 HTTP/2 或 HTTP/3 上,因此 HTTP/2 并不是 gRPC 独占的优势。
gRPC 的优势是把 HTTP/2、protobuf、流式通信和代码生成整合成了统一规范。
6. Deadline 和取消传播
微服务系统经常出现调用链:
API Gateway
→ Order Service
→ User Service
→ Inventory Service
→ Payment Service
→ Risk Service
假设最外层请求只允许执行 800ms。
在 gRPC 中可以设置 deadline:
请求必须在 800ms 内完成
这个 deadline 可以沿调用链传播。
如果网关请求已经取消,内部服务也可以停止工作:
客户端取消
→ Order Service 取消
→ Inventory Service 取消
→ Payment Service 取消
这样可以避免:
- 客户端已经断开,服务端还在继续计算
- 上游已经超时,下游还在继续访问数据库
- 无意义请求占用线程和连接
- 超时级联导致资源耗尽
REST 也能实现,但通常需要各团队自己统一超时和取消机制。
7. 接口演进规则更明确
protobuf 字段使用编号:
message User {
int64 id = 1;
string name = 2;
}
以后增加字段:
message User {
int64 id = 1;
string name = 2;
string avatar_url = 3;
}
老客户端不认识字段 3,通常会忽略它。
如果删除字段,应该保留编号:
message User {
reserved 2;
reserved "name";
int64 id = 1;
string avatar_url = 3;
}
这使接口兼容性更容易被工程化管理。
REST JSON 也可以向后兼容,但缺少统一约束时,团队容易直接修改字段语义。
五、具体业务场景怎么选
场景一:浏览器前端调用后端
例如:
React / Vue / Svelte
→ API
→ 数据库
通常优先选择 REST、GraphQL 或 tRPC,而不是原生 gRPC。
原因:
- 浏览器不能像后端服务一样完整地调用原生 gRPC
- 浏览器对 HTTP JSON 支持最好
- 调试方便
- CDN、鉴权、API Gateway 兼容性好
- 数据规模通常没有大到必须使用 protobuf
典型接口:
POST /api/login
GET /api/profile
GET /api/orders
POST /api/orders
虽然存在 gRPC-Web,但它会增加代理层和调试复杂度,很多普通 Web 项目没有必要使用。
场景二:面向第三方的开放 API
例如:
- Stripe 风格支付 API
- SaaS 平台 API
- GitHub 风格 API
- 内容平台 API
- 电商开放平台
优先 REST。
原因:
- 客户接入成本低
- 文档容易理解
- curl 就能调试
- 各种语言都能接入
- 客户不需要学习 protobuf 工具链
- 更适合 OAuth、Webhook 和 API Key
例如:
POST /v1/payments
GET /v1/payments/{id}
有些企业会同时提供:
- REST API:面向公众
- gRPC API:面向高级客户或内部系统
场景三:内部微服务调用
例如:
User Service
Order Service
Payment Service
Inventory Service
Risk Service
Notification Service
这里 gRPC 很合适。
原因:
- 服务数量多
- 调用频繁
- 团队需要强类型契约
- 需要自动生成 SDK
- 需要统一错误处理
- 需要 deadline、重试、负载均衡
- 可能涉及多语言服务
例如:
service InventoryService {
rpc ReserveStock(ReserveStockRequest)
returns (ReserveStockResponse);
rpc ReleaseStock(ReleaseStockRequest)
returns (ReleaseStockResponse);
}
订单服务直接使用生成的客户端:
await inventoryClient.reserveStock({
orderId,
productId,
quantity,
});
这种场景是 gRPC 最典型的应用领域。
场景四:高频、低延迟的小消息调用
例如:
- 广告实时竞价
- 推荐系统在线推理
- 风控决策
- 搜索召回
- 实时定价
- 游戏匹配
- 游戏内部服务
- 高频遥测
一次调用的数据可能很小,但调用量非常大:
每秒 10 万次请求
每次请求几百字节
此时 JSON 的字段冗余、解析开销和连接管理会逐渐成为成本。
gRPC 的优势会更明显:
- protobuf 更小
- 长连接
- HTTP/2 多路复用
- 代码生成
- 明确 deadline
- 更稳定的接口契约
场景五:机器学习模型推理
例如:
业务服务
→ 模型推理服务
→ GPU
模型服务接口可能是:
service ModelService {
rpc Predict(PredictRequest) returns (PredictResponse);
rpc BatchPredict(BatchPredictRequest)
returns (BatchPredictResponse);
}
适合 gRPC 的原因:
- 请求量高
- 输入可能包含大量数值
- 需要批量调用
- 需要严格定义张量或特征格式
- 对延迟敏感
- 可能使用 Python、C++、Go、Java 等多语言
很多模型服务框架也会同时提供 REST 和 gRPC:
- REST 用于调试和普通调用
- gRPC 用于高性能生产调用
场景六:实时数据流
例如:
- 日志采集
- 实时监控
- 设备遥测
- AI 流式生成
- 服务器事件推送
- 分布式任务进度
gRPC Streaming 比普通 REST 更自然:
rpc StreamMetrics(stream MetricPoint)
returns (MetricSummary);
客户端持续上传:
CPU: 70%
CPU: 75%
CPU: 82%
CPU: 91%
服务端不需要每条数据都重新建立一个独立请求。
但如果涉及浏览器,常见选择仍然可能是:
- SSE
- WebSocket
- WebTransport
- HTTP Streaming
而不是 gRPC。
场景七:游戏服务
游戏系统需要进一步拆开看。
账号、商城、配置、排行榜查询
例如:
登录
购买商品
查询角色
领取奖励
查询排行榜
REST 或 gRPC 都可以。
对外网客户端接口,REST 通常更容易:
POST /login
GET /players/{id}
POST /orders
GET /leaderboards/global
内部游戏微服务
例如:
Gateway
→ Account Service
→ Matchmaking Service
→ Inventory Service
→ Guild Service
→ Payment Service
gRPC 很合适:
service MatchmakingService {
rpc JoinQueue(JoinQueueRequest)
returns (JoinQueueResponse);
}
这里有大量内部调用,强契约和代码生成价值较高。
实时战斗同步
对于高频实时战斗:
客户端位置同步
技能释放
帧同步
状态广播
服务器 tick
通常不会直接使用普通 REST。
gRPC Streaming 理论上可以用,但也不一定是最佳选择,因为游戏实时网络往往需要:
- UDP
- QUIC
- 自定义二进制协议
- WebSocket
- WebTransport
- ENet
- KCP
- 自定义可靠 UDP
- Actor 消息系统
原因是实时战斗需要控制:
- 丢包
- 重传
- 顺序
- 延迟
- 抖动
- Tick
- 快照
- 增量状态
- 广播
- 房间 fan-out
gRPC 更适合服务端内部 RPC,不一定适合所有游戏客户端实时同步。
一种常见架构是:
Web/游戏客户端
│
├── HTTPS REST:登录、商城、配置
│
└── WebSocket/UDP:实时战斗
│
Gateway
│
gRPC / Actor 消息
│
内部微服务与房间服务器
六、REST 不能做这些吗
大部分都能做。
REST 可以配合:
- OpenAPI 实现接口契约
- JSON Schema 做数据校验
- HTTP/2 做多路复用
- SSE 做服务端推送
- WebSocket 做双向通信
- MessagePack 或 protobuf 做二进制序列化
- SDK Generator 生成客户端
- Trace Header 传播链路追踪
- AbortSignal 实现取消
- API Gateway 实现重试和超时
所以问题不是:
REST 能不能实现?
而是:
为了实现这些能力,需要自己组合多少组件,并确保多少团队都遵守同一规范?
gRPC 的价值在于提供一套比较完整且统一的方案:
IDL
+ 代码生成
+ protobuf
+ HTTP/2
+ 流式通信
+ Deadline
+ Cancel
+ Metadata
+ 标准错误码
七、为什么不全部使用 gRPC
因为 gRPC 也有明显缺点。
1. 调试不如 REST 直观
REST:
curl https://api.example.com/users/123
gRPC 通常需要:
- proto 文件
- grpcurl
- BloomRPC
- Postman gRPC
- 服务反射
- 对应客户端代码
二进制数据也不能直接阅读。
2. 浏览器支持受限
浏览器不能像后端程序一样直接完整使用原生 gRPC,通常需要:
Browser
→ gRPC-Web
→ Envoy / Gateway
→ gRPC Service
或者使用 Connect、JSON 转码等方案。
对于普通网页来说,复杂度可能高于收益。
3. CDN 和 HTTP 缓存不自然
REST 的 GET 请求天然适合缓存:
GET /assets/config.json
而 RPC 调用:
ConfigService.GetConfig()
对传统 CDN 和 HTTP 缓存体系不如 REST 直观。
4. 对第三方开发者不友好
如果你要求客户先安装 protobuf 编译器、生成 SDK、维护 proto 版本,接入门槛明显高于:
curl -X POST ...
5. 基础设施更复杂
gRPC 需要整个链路正确支持:
- HTTP/2
- 长连接
- 流式响应
- Trailer
- 负载均衡
- 健康检查
- 代理配置
- 超时配置
部分旧网关、Ingress、WAF 或代理可能处理不好。
6. 不适合简单系统
只有一个 Web 前端加一个后端:
React → Hono → PostgreSQL
这种项目引入 gRPC 通常没有必要。
你会额外增加:
- proto 文件
- 编译步骤
- 生成代码
- 网关
- 浏览器适配
- 调试工具
- 服务发现
但实际性能收益可能几乎不可见。
八、REST 和 gRPC 的典型组合
大型系统很少只选一种,而是分层使用。
外部客户端
Web / App / 第三方开发者
│
REST / GraphQL
│
API Gateway
│
┌──────────────┼──────────────┐
│ │ │
User Service Order Service Payment Service
│ │ │
└────── gRPC / RPC ───────────┘
│
消息队列 / 事件总线
一般是:
- 南北向流量:REST
- 东西向流量:gRPC
- 异步通信:Kafka、RabbitMQ、NATS 等消息系统
这里:
南北向流量
指外部客户端进入系统:
用户 → 网关 → 后端
更重视:
- 兼容性
- 安全
- 可调试性
- 版本管理
- 开放性
因此常用 REST。
东西向流量
指数据中心内部服务互相调用:
订单服务 → 库存服务
订单服务 → 支付服务
更重视:
- 性能
- 强类型
- 自动生成代码
- 超时
- 重试
- 链路追踪
因此常用 gRPC。
九、RPC 与消息队列也不是一回事
gRPC、REST 都属于同步调用模型:
调用方发送请求
调用方等待结果
例如:
Order Service
→ Inventory Service
← ReserveStockResponse
而消息队列是异步模型:
Order Service
→ OrderCreated Event
→ Kafka
→ 多个消费者处理
调用方通常不等待某个具体服务立即返回结果。
适合消息队列的场景:
- 订单创建后发送邮件
- 用户注册后发放奖励
- 数据同步
- 日志采集
- 异步任务
- 削峰填谷
- 一对多事件通知
因此完整系统通常同时存在:
REST:外部接口
gRPC:内部同步调用
Kafka/NATS:内部异步事件
WebSocket/UDP:实时连接
十、一个实际例子:下单系统
用户通过前端创建订单:
POST /api/orders
Content-Type: application/json
{
"productId": "p1001",
"quantity": 2
}
这是 REST,因为它是面向浏览器的公共接口。
API 服务内部调用库存:
rpc ReserveStock(ReserveStockRequest)
returns (ReserveStockResponse);
使用 gRPC,因为是内部高频服务调用。
支付成功后发送事件:
PaymentSucceeded
通过 Kafka 或 NATS 通知:
- 订单服务更新状态
- 通知服务发送邮件
- 积分服务增加积分
- 数据仓库记录交易
这部分使用异步消息,而不是继续一层层 RPC。
完整链路:
Browser
│ REST
▼
Order API
│ gRPC
├────────► Inventory Service
│ gRPC
└────────► Payment Service
│
│ Event
▼
Kafka
┌──────┼──────┐
▼ ▼ ▼
Email Points Analytics
不同通信方式分别解决不同问题。
十一、选型判断表
| 场景 | 推荐 |
|---|---|
| 浏览器调用后端 | REST / GraphQL / tRPC |
| 公共开放 API | REST |
| 简单管理后台 | REST |
| 单体应用 | REST 或直接函数调用 |
| 内部微服务同步调用 | gRPC |
| 多语言微服务 | gRPC |
| 高频小包调用 | gRPC |
| 服务端流式返回 | gRPC Streaming / SSE |
| 浏览器双向实时通信 | WebSocket / WebTransport |
| 游戏实时战斗 | UDP / QUIC / WebSocket / 自定义协议 |
| 服务端游戏微服务 | gRPC / Actor RPC |
| 异步任务 | 消息队列 |
| 事件广播 | Kafka / NATS / Pulsar |
| 第三方 Webhook | REST |
| CDN 可缓存资源 | REST |
十二、最实用的选型原则
可以按下面顺序判断。
默认先用 REST
以下情况不需要急着引入 gRPC:
- 服务数量少
- 调用量不高
- 主要是 CRUD
- 浏览器直接调用
- 对外开放接口
- 团队规模小
- 性能没有出现瓶颈
REST 的简单性本身就是优势。
出现明确问题后再考虑 gRPC
当你开始遇到这些问题时,gRPC 的价值会提高:
- 微服务数量越来越多
- OpenAPI 与代码经常不一致
- 多语言 SDK 维护困难
- 内部调用量很大
- JSON 序列化和带宽成本明显
- 需要双向流
- 需要统一 deadline 和取消传播
- 需要严格接口兼容规则
- 希望自动生成客户端
- 内部服务之间存在大量高频调用
不要只因为“性能更高”就使用 gRPC
很多系统的主要耗时在:
- 数据库
- Redis
- 外部 API
- 磁盘 IO
- 锁竞争
- 业务计算
- 网络跨区域延迟
例如一次请求:
JSON 解析:0.2ms
数据库查询:80ms
跨区域网络:50ms
把 JSON 换成 protobuf,并不会让 130ms 的请求变成 10ms。
所以 gRPC 更大的价值通常是:
强契约、代码生成、统一调用模型和微服务治理,而不仅仅是序列化性能。
十三、对个人开发者或中小项目的建议
对于你的 Phaser、Hono、Bun、Colyseus 这类技术栈,可以采用:
Phaser 客户端
├── REST/HTTP:登录、配置、商城、存档
└── WebSocket:房间、游戏状态、实时消息
Hono API
├── PostgreSQL / D1
├── Redis
└── 内部服务较少时直接调用
Colyseus
└── 房间状态同步
早期没有必要强行引入 gRPC。
当系统真正拆成多个独立服务后:
API Gateway
Account Service
Matchmaking Service
Inventory Service
Payment Service
Analytics Service
再考虑:
外部:REST + WebSocket
内部:gRPC
异步:NATS / Kafka
更合理。
一句话概括:
REST 优先解决“开放、通用、易接入”,gRPC 优先解决“内部、强契约、高频和流式通信”,消息队列解决“异步、解耦和事件广播”,WebSocket/UDP 解决“实时长连接”。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。