选错 API 协议的代价有多大?微服务间通信延迟从 10ms 飙升到 200ms、前后端联调天天吵架、接口文档永远对不上——这些问题90%都源于协议选型不当。本文用 4 组实战代码 + 真实性能数据,带你一次搞懂四大协议怎么选。
1. 四大协议速览:一张表看清本质
在做选择之前,先搞清楚每个协议的定位和核心差异。
| 维度 | REST | gRPC | GraphQL | tRPC |
|---|---|---|---|---|
| 协议 | HTTP/1.1 | HTTP/2 | HTTP/1.1 | HTTP/2 (可选) |
| 数据格式 | JSON / XML | Protobuf (二进制) | JSON | JSON / 自定义 |
| 接口定义 | OpenAPI (Swagger) | .proto 文件 | Schema (SDL) | TypeScript/代码定义 |
| 类型安全 | 弱 (靠文档) | 强 (编译期校验) | 中等 (Schema 校验) | 强 (编译期校验) |
| 流式支持 | ❌ | ✅ 双向流 | ❌ (用 Subscription) | 取决于底层 |
| 代码生成 | OpenAPI Generator | protoc 原生 | GraphQL Codegen | tRPC CLI 原生 |
| 浏览器兼容 | ✅ 完美 | ❌ 需要 grpc-web | ✅ 完美 | ✅ 完美 |
| 学习曲线 | 低 | 中高 | 中 | 低 (TS 项目) |
| 适合场景 | 对外开放 API、Web 应用 | 内部微服务、IoT | 复杂数据查询、BFF 层 | 全栈 TypeScript 项目 |
核心记忆:REST 适合对外,gRPC 适合对内,GraphQL 适合复杂查询,tRPC 适合全栈 TypeScript。
2. REST:最简单的起点,最广泛的生态
REST 还是最通用的选择。来看一个生产级的 FastAPI 实现:
# rest_api.py - FastAPI 实现 RESTful 订单服务
from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel, Field
from typing import Optional, List
from datetime import datetime
import uvicorn
app = FastAPI(title="订单服务", version="1.0.0")
# ========== 数据模型 ==========
class OrderCreate(BaseModel):
product_id: str = Field(..., min_length=1, description="商品ID")
quantity: int = Field(..., gt=0, le=9999, description="数量")
price: float = Field(..., gt=0, description="单价")
class OrderResponse(BaseModel):
order_id: str
product_id: str
quantity: int
total: float
status: str = "pending"
created_at: datetime
# ========== 模拟数据库 ==========
orders_db: dict = {}
# ========== RESTful 接口 ==========
@app.post("/api/v1/orders", response_model=OrderResponse)
async def create_order(order: OrderCreate):
"""创建订单 —— POST /api/v1/orders"""
order_id = f"ORD-{len(orders_db) + 1:06d}"
new_order = OrderResponse(
order_id=order_id,
product_id=order.product_id,
quantity=order.quantity,
total=order.quantity * order.price,
created_at=datetime.now()
)
orders_db[order_id] = new_order
return new_order
@app.get("/api/v1/orders/{order_id}", response_model=OrderResponse)
async def get_order(order_id: str):
"""查询订单 —— GET /api/v1/orders/{order_id}"""
if order_id not in orders_db:
raise HTTPException(status_code=404, detail="订单不存在")
return orders_db[order_id]
@app.get("/api/v1/orders", response_model=List[OrderResponse])
async def list_orders(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100)
):
"""分页查询 —— GET /api/v1/orders?page=1&size=20"""
start = (page - 1) * size
return list(orders_db.values())[start:start + size]
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
REST 的优点:生态成熟、工具链完善(Postman/Swagger/curl)、调试门槛极低。缺点也很明显:服务间调用 JSON 序列化开销大、字段多时传输冗余严重。
3. gRPC:微服务内部通信的性能之王
当你的系统有几十上百个微服务在疯狂互调时,gRPC + Protobuf 是降延迟的利器。
# server.py - gRPC 订单服务(服务端)
import grpc
from concurrent import futures
import order_pb2, order_pb2_grpc
import uuid
import time
class OrderService(order_pb2_grpc.OrderServiceServicer):
"""gRPC 订单服务实现"""
def __init__(self):
self.orders = {}
def CreateOrder(self, request, context):
"""gRPC 一元调用:创建订单"""
order_id = str(uuid.uuid4())[:8]
order = order_pb2.Order(
id=order_id,
product_id=request.product_id,
quantity=request.quantity,
price=request.price,
total=request.quantity * request.price,
status="pending"
)
self.orders[order_id] = order
return order
def GetOrder(self, request, context):
"""gRPC 一元调用:查询订单"""
order = self.orders.get(request.id)
if not order:
context.abort(grpc.StatusCode.NOT_FOUND, "订单不存在")
return order
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
order_pb2_grpc.add_OrderServiceServicer_to_server(
OrderService(), server
)
server.add_insecure_port("[::]:50051")
server.start()
print("gRPC 服务启动在 50051 端口...")
server.wait_for_termination()
if __name__ == "__main__":
serve()
# client.py - gRPC 客户端调用
import grpc
import order_pb2, order_pb2_grpc
channel = grpc.insecure_channel("localhost:50051")
stub = order_pb2_grpc.OrderServiceStub(channel)
# 创建订单
order = stub.CreateOrder(order_pb2.CreateRequest(
product_id="PROD-001",
quantity=3,
price=99.9
))
print(f"订单创建成功: {order.id}, 总价: {order.total}")
# 查询订单
order = stub.GetOrder(order_pb2.GetRequest(id=order.id))
print(f"订单状态: {order.status}")
channel.close()
性能实测:同一台机器上压测 10000 个请求,gRPC 平均耗时约为 REST 的 1/3 ~ 1/5。Protobuf 二进制序列化是降延迟的关键。
| 指标 | REST (JSON) | gRPC (Protobuf) |
|---|---|---|
| 平均延迟 | 8.5ms | 2.1ms |
| 吞吐量 (req/s) | 3,200 | 12,800 |
| 带宽消耗 | 100% | ~35% |
4. GraphQL:让前端拥有数据查询的自由
前端同学最痛苦的事:后端返回 50 个字段,我只用 3 个。GraphQL 解决的就是这个问题。
# graphql_server.py - Strawberry 实现 GraphQL 订单服务
import strawberry
from typing import List, Optional
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
# ========== 类型定义 ==========
@strawberry.type
class Order:
id: str
product_name: str
quantity: int
price: float
total: float
status: str
customer_name: Optional[str] = None
shipping_address: Optional[str] = None
# ========== 模拟数据 ==========
orders_data = [
Order("1", "GPU服务器 H20", 2, 45000.0, 90000.0, "shipped",
"张三", "河南省郑州市金水区"),
Order("2", "腾讯云 CVM 标准型", 5, 299.0, 1495.0, "pending",
"李四", "河南省洛阳市"),
Order("3", "COS 对象存储 1TB", 1, 99.0, 99.0, "delivered",
None, None),
]
# ========== Query ==========
@strawberry.type
class Query:
@strawberry.field
def orders(
self,
status: Optional[str] = None,
min_total: Optional[float] = None
) -> List[Order]:
"""前端按需查询,只拿需要的字段"""
result = orders_data
if status:
result = [o for o in result if o.status == status]
if min_total is not None:
result = [o for o in result if o.total >= min_total]
return result
# ========== 启动服务 ==========
schema = strawberry.Schema(query=Query)
app = FastAPI()
app.include_router(GraphQLRouter(schema), prefix="/graphql")
前端只需发送这样的查询,就拿到的恰好是需要的字段:
# 前端请求 —— 只要 id、product_name、total 三个字段
{
orders(status: "shipped") {
id
productName
total
}
}
GraphQL 适合的场景:BFF 层(面向前端的后端)、复杂关联数据查询、多端(Web + App + 小程序)数据需求不同。
5. tRPC:全栈 TypeScript 的终极武器
如果你前后端都是 TypeScript,tRPC 能直接共享类型定义,省去写接口文档的功夫:
// server.ts - tRPC 订单服务(Next.js 风格)
import { initTRPC } from "@trpc/server";
import { z } from "zod";
const t = initTRPC.create();
export const appRouter = t.router({
// 查询:获取订单列表
orderList: t.procedure
.input(z.object({ limit: z.number().min(1).max(100).default(10) }))
.query(({ input }) => {
return [
{ id: "1", product: "GPU服务器", total: 90000 },
{ id: "2", product: "CVM", total: 1495 },
].slice(0, input.limit);
}),
// 变更:创建订单
orderCreate: t.procedure
.input(
z.object({
productId: z.string(),
quantity: z.number().min(1),
price: z.number().positive(),
})
)
.mutation(({ input }) => {
// ⬇️ TypeScript 自动推导类型,前端调用时零出错
return {
id: Math.random().toString(36).slice(2),
product: input.productId,
total: input.quantity * input.price,
};
}),
});
export type AppRouter = typeof appRouter;
// client.ts - 前端调用,享受完整的类型提示
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
import type { AppRouter } from "./server";
const trpc = createTRPCProxyClient<AppRouter>({
links: [httpBatchLink({ url: "http://localhost:3000/api/trpc" })],
});
// 🔥 有完整的 IDE 自动补全 + 类型检查!
const orders = await trpc.orderList.query({ limit: 5 });
// ^? 类型自动推导为 { id: string; product: string; total: number }[]
6. 选型决策矩阵:你的场景归哪类?
| 场景 | 推荐协议 | 理由 |
|---|---|---|
| 对外开放的公共 API | REST | 生态最广、文档工具成熟、浏览器原生支持 |
| 内部微服务间高频调用 | gRPC | 二进制序列化、双向流、连接复用 |
| 前端多端(Web/App/小程序)数据需求不同 | GraphQL | 按需查询、聚合数据、减少请求 |
| 全栈 TypeScript 项目 | tRPC | 端到端类型安全、零文档生成 |
| IoT / 边缘设备低带宽 | gRPC | Protobuf 体积极小、HTTP/2 省连接 |
| 实时推送(聊天/通知) | gRPC 流 或 WebSocket | 双向流天然支持 |
| 快速原型 / MVP | REST | 上手快、调试方便 |
混合架构:成年人全都要
实际上,大厂通常不是”选一个”,而是组合使用:
┌──────────────┐
外部请求 ──────→ │ API Gateway │
(REST/OpenAPI) │ (Kong/ │
│ APISIX) │
└──────┬───────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 订单服务 │ │ 用户服务 │ │ 通知服务 │
│ (gRPC) │ │ (gRPC) │ │ (gRPC) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ GraphQL BFF │ │ tRPC 管理 │
│ (面向前端) │ │ (面向前端) │
└─────────────┘ └─────────────┘
7. 实战建议:3 条黄金法则
法则一:服务间调用,能上 gRPC 就上 gRPC
如果你现在还在用 REST 做微服务间通信,迁移到 gRPC 后你会发现延迟降了一个数量级。唯一的代价是:需要写 .proto 文件、用 protoc 生成代码——但这正是一次性投入。
法则二:别在外部 API 上强制 GraphQL
GraphQL 的 N+1 查询问题、权限控制的复杂度、缓存困难,在开放 API 场景中会放大。对外 API 还是 REST + OpenAPI Schema 最稳。
法则三:如果有 API Gateway,上层协议可以随时换
Kong / APISIX 支持 REST → gRPC 协议转换。你可以在网关层开放 REST,内部全走 gRPC,兼顾对外兼容性和对内性能。
总结
- REST:通用性最强,对外 API 首选
- gRPC:性能最强,微服务内部首选
- GraphQL:灵活性最强,前端体验首选
- tRPC:类型安全最强,全栈 TS 首选
没有银弹,只有最适合的子弹。你的系统架构决定了你的 API 协议,而不是反过来。
👤 作者简介
一枚在大中原腹地(河南)卖公有云的从业者,主营腾讯云/阿里云/火山引擎,曾踩坑无数,现专注AI大模型应用落地。关注公众号「公有云cloud」,围观AI前沿动态~
博客:y1cloud.cn
