REST API 与 gRPC:从资源接口到高性能 RPC 的架构选择

深入解析 REST API 与 gRPC 等 RPC 框架的核心差异,涵盖 HTTP、REST、RPC、Protocol Buffers、HTTP/2、流式通信、微服务调用与游戏服务架构,并结合实际场景给出接口通信方案的选型建议。

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 中字段名会重复传输:

  • userId
  • displayName
  • accountStatus

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
公共开放 APIREST
简单管理后台REST
单体应用REST 或直接函数调用
内部微服务同步调用gRPC
多语言微服务gRPC
高频小包调用gRPC
服务端流式返回gRPC Streaming / SSE
浏览器双向实时通信WebSocket / WebTransport
游戏实时战斗UDP / QUIC / WebSocket / 自定义协议
服务端游戏微服务gRPC / Actor RPC
异步任务消息队列
事件广播Kafka / NATS / Pulsar
第三方 WebhookREST
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 解决“实时长连接”。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页