协议战争有一个细致入微的答案
在2020年,对于”gRPC与REST哪个更好?”这个问题的回答通常是”视情况而定,但REST对大多数场景来说已经足够好了”。到了2026年,随着AI推理端点、高吞吐量微服务网格和流数据管道的普及,计算方式已经发生了变化。REST对大多数场景来说仍然足够好——但gRPC占优的情况已经变得更加常见。本指南为您提供了一个具体的决策框架,基于真实的基准测试和实际的权衡取舍。
gRPC到底是什么(以及不是什么)
gRPC是一个构建在HTTP/2和Protocol Buffers(protobuf)之上的远程过程调用框架。当人们说”gRPC比REST更快”时,他们通常是与HTTP/1.1上的JSON进行比较——这混淆了两个变量。准确地说:
- 协议:HTTP/2(gRPC)与HTTP/1.1或HTTP/2(REST)
- 序列化:Protobuf(gRPC)与JSON(通常是REST)
- 流式传输:内置双向流(gRPC)与SSE/WebSockets(REST)
- 代码生成:基于模式、强类型(gRPC)与可选(REST + OpenAPI)
大部分性能优势来自于protobuf序列化和HTTP/2多路复用,而非gRPC本身。
基准测试现实:数字何时真正重要
在4核VPS(典型小型团队基础设施)上的受控基准测试中,比较一个简单的用户查找端点:
- HTTP/1.1上的JSON:约8,000请求/秒,约5ms p99延迟
- HTTP/2上的JSON:约11,000请求/秒,约3ms p99延迟
- gRPC(protobuf):约18,000请求/秒,约1.5ms p99延迟
- gRPC(protobuf,流式):对于小消息流,约35,000事件/秒
2倍的吞吐量提升听起来很显著。但如果您的服务每秒处理500个请求,而您的瓶颈是需要20ms的数据库查询,那么序列化格式就无关紧要了。
当gRPC的性能优势很重要时:
- 您的服务每秒请求数超过5,000
- 负载大小很大(100KB+)且您每秒需要序列化/反序列化数千次
- 您正在使用大型嵌入向量或张量进行AI推理
- 您有许多小消息,可以从HTTP/2多路复用中受益
使用 Protobuf 定义您的服务契约
// user_service.proto
syntax = "proto3";
package userservice.v1;
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
rpc StreamUserActivity (StreamActivityRequest) returns (stream ActivityEvent);
rpc BatchGetUsers (BatchGetUsersRequest) returns (stream User);
}
message GetUserRequest {
string user_id = 1;
}
message User {
string id = 1;
string email = 2;
string display_name = 3;
int64 created_at_unix = 4;
UserRole role = 5;
map<string, string> metadata = 6;
}
enum UserRole {
USER_ROLE_UNSPECIFIED = 0;
USER_ROLE_VIEWER = 1;
USER_ROLE_EDITOR = 2;
USER_ROLE_ADMIN = 3;
}
message StreamActivityRequest {
string user_id = 1;
int64 since_unix = 2;
}
message ActivityEvent {
string event_type = 1;
int64 timestamp = 2;
bytes payload = 3; // 序列化的事件数据
}
为您的语言生成代码:
# 生成 Python 存根
python -m grpc_tools.protoc
-I./proto
--python_out=./generated
--grpc_python_out=./generated
proto/user_service.proto
# 生成 Go 存根
protoc
--go_out=./generated
--go-grpc_out=./generated
proto/user_service.proto
在 Python 中实现 gRPC 服务器
import grpc
from concurrent import futures
from generated import user_service_pb2, user_service_pb2_grpc
from opentelemetry.instrumentation.grpc import GrpcInstrumentorServer
class UserServiceServicer(user_service_pb2_grpc.UserServiceServicer):
def GetUser(self, request, context):
user = db.get_user(request.user_id)
if not user:
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details(f"User {request.user_id} not found")
return user_service_pb2.User()
return user_service_pb2.User(
id=user.id,
email=user.email,
display_name=user.display_name,
created_at_unix=int(user.created_at.timestamp()),
role=user_service_pb2.USER_ROLE_ADMIN if user.is_admin
else user_service_pb2.USER_ROLE_VIEWER
)
def StreamUserActivity(self, request, context):
"""服务器端流式传输:在事件发生时推送事件"""
with db.subscribe_to_user_activity(request.user_id,
since=request.since_unix) as events:
for event in events:
if context.is_active():
yield user_service_pb2.ActivityEvent(
event_type=event.type,
timestamp=int(event.timestamp.timestamp()),
payload=event.serialize()
)
else:
break # 客户端断开连接
def serve():
# 使用 OpenTelemetry 进行检测
GrpcInstrumentorServer().instrument()
server = grpc.server(
futures.ThreadPoolExecutor(max_workers=10),
options=[
('grpc.max_receive_message_length', 100 * 1024 * 1024), # 100MB
('grpc.keepalive_time_ms', 30000),
('grpc.keepalive_timeout_ms', 5000),
]
)
user_service_pb2_grpc.add_UserServiceServicer_to_server(
UserServiceServicer(), server
)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
gRPC 流式传输:AI 的杀手级应用场景
流式传输是 gRPC 真正没有 REST 等效功能的地方。LLM 推理是完美示例 — 不用等待完整完成结果,而是在生成时流式传输 token:
// inference.proto
service InferenceService {
rpc Complete (CompleteRequest) returns (CompleteResponse); // 一元调用
rpc StreamComplete (CompleteRequest) returns (stream TokenChunk); // 服务器流式传输
rpc Chat (stream ChatMessage) returns (stream TokenChunk); // 双向流式传输
}
message TokenChunk {
string text = 1;
bool is_final = 2;
int32 tokens_generated = 3;
}
# Python gRPC 客户端(服务器流式传输)
import grpc
from generated import inference_pb2, inference_pb2_grpc
channel = grpc.secure_channel('inference.internal:443', grpc.ssl_channel_credentials())
stub = inference_pb2_grpc.InferenceServiceStub(channel)
request = inference_pb2.CompleteRequest(
prompt="解释分布式系统中的断路器",
max_tokens=500,
temperature=0.7
)
# 随着 token 到达进行流式传输
full_response = []
for chunk in stub.StreamComplete(request):
print(chunk.text, end='', flush=True)
full_response.append(chunk.text)
if chunk.is_final:
print(f"n[生成了 {chunk.tokens_generated} 个 token]")
break
何时 REST 仍然是正确选择
gRPC 有真正的缺点,在许多情况下使 REST 成为更好的选择:
公共 API
gRPC 需要生成客户端存根。REST + JSON 可以从 curl、浏览器的 fetch API、Postman 或任何无需设置的 HTTP 客户端调用。对于公共 API 或由第三方使用的 API,REST 的采用要容易得多。
浏览器客户端
gRPC-Web 需要在浏览器和您的 gRPC 服务之间设置代理(Envoy 或 grpc-web-proxy)。浏览器的 HTTP/2 多路复用被浏览器限制所阻止。如果您的客户端主要是网页浏览器,REST 或 GraphQL 更简单。
简单的 CRUD 服务
维护 .proto 文件、生成存根和管理 protobuf 模式的运营开销,对于处理中等流量的简单 CRUD API(5-10 个端点)来说不值得。REST + OpenAPI 规范以更少的仪式为您提供代码生成、验证和文档。
调试和测试
在 Wireshark、curl 或浏览器 DevTools 中检查 REST 流量非常简单。二进制 protobuf 需要 grpcurl 或 grpc-ui 等工具:
# grpcurl 用于测试 gRPC 端点(类似于 gRPC 的 curl)
grpcurl -plaintext
-d '{"user_id": "usr_123"}'
localhost:50051
userservice.v1.UserService/GetUser
# grpc-ui 提供用于探索 gRPC 服务的 Web UI
grpcui -plaintext localhost:50051
迁移路径:向现有的 REST 服务添加 gRPC
你不必二选一——许多生产系统同时运行两者。常见模式:内部服务间通信使用 gRPC,外部 API 使用 REST。
# 混合服务的 docker-compose.yml
services:
api:
image: myapp
ports:
- "8080:8080" # 外部客户端的 REST API
- "50051:50051" # 内部服务网格的 gRPC
environment:
GRPC_PORT: 50051
HTTP_PORT: 8080
Envoy 也能处理 gRPC-JSON 转码,使用你的 .proto 定义自动在 REST 和 gRPC 之间转换——在迁移过程中很有用。
决策框架:2026 年 gRPC 与 REST 的选择
在以下情况使用 gRPC:
- 微服务网格中的服务间通信
- 高吞吐量(>5K 请求/秒)的内部 API
- 流式数据(LLM 推理、实时事件、遥测管道)
- 强类型契约至关重要(多团队、多语言环境)
- 大型二进制负载(嵌入、图像、ML 张量)
在以下情况使用 REST:
- 公共 API 或第三方集成
- 以浏览器为主的客户端
- 中等规模下的简单 CRUD 操作
- 团队不熟悉 protobuf 工作流
- 快速原型设计,其中优先考虑模式会增加摩擦
2026 年的务实答案:从 REST 开始,随着规模扩大,为内部高吞吐量路径添加 gRPC。这些模式并非互斥——在各自适合的地方同时运行。
