强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

HTTP/2 与 RPC 精讲教程 / 11 - REST vs gRPC 选型

第 11 章:REST vs gRPC 选型指南

没有银弹——理解差异,选对方案


11.1 核心差异对比

11.1.1 技术维度对比

维度REST/JSONgRPC/Protobuf
协议HTTP/1.1 或 HTTP/2HTTP/2
数据格式JSON (文本)Protobuf (二进制)
接口定义OpenAPI/Swagger.proto 文件 (IDL)
浏览器支持原生支持需要 gRPC-Web 代理
工具生态curl、Postmangrpcurl、grpcui
学习曲线中(需学习 Protobuf)
代码生成可选(如 openapi-generator)必须(protoc)
向后兼容手动管理内建机制(字段编号)
流式传输WebSocket / SSE四种原生模式
错误处理HTTP 状态码gRPC 状态码 + Details

11.1.2 性能对比数据

测试环境:4 核 CPU,16GB 内存,内网环境
测试内容:10,000 次请求/响应

┌─────────────────────┬──────────┬──────────┬──────────┐
│ 指标                │ REST     │ gRPC     │ 差异     │
├─────────────────────┼──────────┼──────────┼──────────┤
│ 单次请求延迟 P50    │ 1.2ms    │ 0.3ms    │ 4x       │
│ 单次请求延迟 P99    │ 3.5ms    │ 0.8ms    │ 4.4x     │
│ 最大吞吐量 (QPS)    │ 15,000   │ 85,000   │ 5.7x     │
│ 序列化大小 (1KB)    │ 1,024B   │ 320B     │ 3.2x     │
│ 序列化速度          │ 基准     │ 5-10x    │ -        │
│ 内存占用 (每连接)   │ ~8KB     │ ~12KB    │ 1.5x     │
│ CPU 占用 (高并发)   │ 基准     │ 0.3x     │ -        │
└─────────────────────┴──────────┴──────────┴──────────┘

注意:性能数据因场景、负载、环境不同会有显著差异

11.2 选型决策矩阵

11.2.1 场景匹配表

场景RESTgRPC理由
公开 API(第三方开发者)生态兼容性、可调试性
浏览器直接调用原生支持
移动端后端 API⚠️节省带宽,性能好
微服务内部通信⚠️高性能,类型安全
实时流式通信原生支持
简单 CRUD 应用⚠️开发效率高
低延迟交易系统序列化快,延迟低
文件/媒体传输⚠️HTTP 原生支持
IoT 设备通信二进制协议节省带宽
快速原型开发无需 IDL 学习成本

11.2.2 决策流程图

开始选型
   │
   ├─ Q1: 需要浏览器直接调用?
   │   ├─ 是 → REST(或 gRPC-Web)
   │   └─ 否 ↓
   │
   ├─ Q2: 需要流式通信?
   │   ├─ 是 → gRPC
   │   └─ 否 ↓
   │
   ├─ Q3: 性能是关键约束?
   │   ├─ 是 → gRPC
   │   └─ 否 ↓
   │
   ├─ Q4: 团队熟悉 Protobuf 吗?
   │   ├─ 否 → REST(降低学习成本)
   │   └─ 是 ↓
   │
   ├─ Q5: 需要强类型契约?
   │   ├─ 是 → gRPC
   │   └─ 否 → REST
   │
   └─ 也可以两者并存(混用架构)

11.3 混合架构模式

11.3.1 典型混用架构

                    ┌──────────────┐
                    │   浏览器/APP  │
                    └──────┬───────┘
                           │ REST / GraphQL
                    ┌──────▼───────┐
                    │  API Gateway  │
                    │  (REST 入口)  │
                    └──────┬───────┘
                           │ gRPC(内部通信)
            ┌──────────────┼──────────────┐
            │              │              │
     ┌──────▼──┐   ┌──────▼──┐   ┌──────▼──┐
     │ 用户服务 │   │ 订单服务 │   │ 支付服务 │
     │ (gRPC)  │   │ (gRPC)  │   │ (gRPC)  │
     └─────────┘   └─────────┘   └─────────┘

原则:
- 外部暴露 REST(兼容性好)
- 内部使用 gRPC(性能好)
- API Gateway 负责协议转换

11.3.2 gRPC-Gateway 实现

// 使用 gRPC-Gateway 同时提供 REST 和 gRPC 接口
package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"net/http"

	pb "example/pb"
	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

// 实现 gRPC 服务
type userService struct {
	pb.UnimplementedUserServiceServer
}

func (s *userService) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
	return &pb.GetUserResponse{
		User: &pb.User{
			Id:    req.Id,
			Name:  "Alice",
			Email: "[email protected]",
		},
	}, nil
}

func main() {
	// 启动 gRPC 服务器
	lis, _ := net.Listen("tcp", ":50051")
	grpcServer := grpc.NewServer()
	pb.RegisterUserServiceServer(grpcServer, &userService{})
	go grpcServer.Serve(lis)

	// 启动 REST 代理
	ctx := context.Background()
	mux := runtime.NewServeMux()
	opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}

	err := pb.RegisterUserServiceHandlerFromEndpoint(ctx, mux, "localhost:50051", opts)
	if err != nil {
		log.Fatalf("注册 REST 代理失败: %v", err)
	}

	// 现在同一个接口同时支持:
	// gRPC: localhost:50051
	// REST: GET localhost:8080/v1/users/1
	fmt.Println("REST 代理启动于 :8080")
	http.ListenAndServe(":8080", mux)
}
// proto 定义 REST 映射
syntax = "proto3";

package example;

import "google/api/annotations.proto";

service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse) {
    option (google.api.http) = {
      get: "/v1/users/{id}"
    };
  }

  rpc CreateUser(CreateUserRequest) returns (User) {
    option (google.api.http) = {
      post: "/v1/users"
      body: "*"
    };
  }

  rpc UpdateUser(UpdateUserRequest) returns (User) {
    option (google.api.http) = {
      put: "/v1/users/{id}"
      body: "*"
    };
  }

  rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty) {
    option (google.api.http) = {
      delete: "/v1/users/{id}"
    };
  }
}

11.4 迁移策略

11.4.1 从 REST 到 gRPC 的渐进迁移

阶段 1:并行运行
┌─────────────────────────────────────────┐
│              API Gateway                 │
│  ┌──────────────┐  ┌──────────────┐     │
│  │ REST (旧客户端)│  │gRPC (新客户端)│     │
│  └──────┬───────┘  └──────┬───────┘     │
│         │                 │              │
│         └────────┬────────┘              │
│                  ▼                       │
│           同一个后端服务                   │
└─────────────────────────────────────────┘

阶段 2:逐步迁移
- 新功能只用 gRPC 开发
- 旧 REST 接口维护不新增功能
- 客户端逐步迁移到 gRPC

阶段 3:清理
- 监控 REST 接口流量,接近零时下线
- 移除 REST 代理层

11.4.2 协议转换代理

// Envoy 代理配置示例(protocol-transcoding)
// envoy.yaml 片段
`
static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address: { address: 0.0.0.0, port_value: 8080 }
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          codec_type: AUTO
          http_filters:
          - name: envoy.filters.http.grpc_json_transcoder
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder
              proto_descriptor: "/etc/envoy/service.pb"
              services: ["example.UserService"]
          - name: envoy.filters.http.router
`

11.5 业务场景对比

11.5.1 电商 API 设计

公开 API(REST):
GET    /v1/products           # 商品列表
GET    /v1/products/{id}      # 商品详情
POST   /v1/orders             # 创建订单
GET    /v1/orders/{id}        # 订单详情

内部服务通信(gRPC):
service OrderService {
  rpc CreateOrder(CreateOrderRequest) returns (Order);
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc StreamOrderUpdates(stream OrderEvent) returns (stream OrderState);
}

11.5.2 移动端后端

移动 APP → API Gateway (REST/gRPC-Web)

考虑因素:
- 弱网环境:gRPC 二进制协议更高效
- 流量成本:Protobuf 更小,节省用户流量
- 电量消耗:更少的数据传输 = 更省电
- 开发效率:代码生成减少手动工作

推荐:移动端使用 gRPC-Web 或 gRPC over REST

11.6 注意事项

⚠️ REST 的陷阱

  • 过度设计 URI 导致接口混乱
  • 缺少版本管理导致兼容性问题
  • JSON 序列化性能在高并发下成为瓶颈

⚠️ gRPC 的陷阱

  • 浏览器支持有限(需要 gRPC-Web 代理)
  • 调试不如 REST 直观(二进制协议)
  • 学习曲线较陡(Protobuf + gRPC 工具链)

💡 实用建议

  • 面向公众的 API 优先选择 REST
  • 内部微服务优先选择 gRPC
  • 两者可以并存,通过 API Gateway 统一管理
  • 不要为了"新潮"而强推 gRPC

11.7 扩展阅读


第 10 章 - gRPC 高级特性 | 第 12 章 - Apache Thrift