Language:Chinese VersionEnglish Version

协议战争有一个细致入微的答案

在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 需要 grpcurlgrpc-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。这些模式并非互斥——在各自适合的地方同时运行。

By Michael Sun

Founder and Editor-in-Chief of NovVista. Software engineer with hands-on experience in cloud infrastructure, full-stack development, and DevOps. Writes about AI tools, developer workflows, server architecture, and the practical side of technology. Based in China.

Leave a Reply

Your email address will not be published. Required fields are marked *

You missed