跳转至

gRPC

gRPC 是一个基于 HTTP/2Protocol Buffers 的高性能 RPC 框架。它把远程服务调用抽象成类似本地函数调用的形式,适合微服务内部通信、跨语言服务集成、低延迟接口和流式数据传输场景。

grpc

Abstract

gRPC 的优势不只是“更快的 HTTP API”,而是提供了一套围绕服务契约代码生成强类型通信流式传输多语言集成的 RPC 体系。

实践中需要重点关注:

  • .proto 是长期契约,演进要保守
  • 每个 RPC 都要有明确 deadline
  • Channel 应该复用
  • 大响应优先考虑 streaming
  • 生产环境必须补齐认证、观测性、限流和优雅下线

背景

在服务拆分之后,系统内部会出现大量服务间通信。直接使用 REST/JSON 可以工作,但在高频调用、强类型接口、跨语言 SDK 和流式传输场景下会遇到一些问题:

  • JSON 编解码开销较高,字段类型约束较弱
  • API 契约通常依赖文档,客户端和服务端容易不一致
  • 双向流、长连接和多路复用能力有限
  • 多语言客户端 SDK 需要手写或额外生成

gRPC 的核心思路

  • .proto 文件定义服务契约
  • 用 Protobuf 作为二进制序列化格式
  • 用 HTTP/2 作为传输层协议
  • 通过代码生成器生成客户端 Stub 和服务端接口

HTTP/2 的关键能力

gRPC 并不是简单地把二进制数据塞进 HTTP 请求,它依赖 HTTP/2 的几个关键能力:

  • 多路复用:同一条 TCP 连接上可以并发多个请求,减少连接数量
  • 流控:客户端和服务端可以控制数据发送速度,避免接收方被压垮
  • 头部压缩:降低频繁调用时的元数据开销
  • 长连接:减少重复建连和 TLS 握手成本
  • 流式传输:天然支持单向流和双向流

HTTP/2 多路复用解决的是连接复用问题,不等于业务层没有并发压力。服务端仍然需要控制线程池、连接数、最大并发流数量和下游资源访问。


核心组件

组件 作用
Protocol Buffers 定义消息结构和服务接口,并提供高效二进制序列化
HTTP/2 提供多路复用、流控、头部压缩和长连接能力
Stub 客户端代理对象,像调用本地方法一样发起 RPC
Service Implementation 服务端真实业务逻辑实现
Channel 客户端到服务端的连接抽象,底层通常复用 HTTP/2 连接
Interceptor 在请求前后插入通用逻辑,如认证、日志、追踪、限流

基本调用流程

sequenceDiagram
    participant Client as Client
    participant Stub as Client Stub
    participant Server as gRPC Server
    participant Impl as Service Impl

    Client->>Stub: 调用本地方法
    Stub->>Stub: Protobuf 序列化
    Stub->>Server: HTTP/2 请求
    Server->>Server: Protobuf 反序列化
    Server->>Impl: 调用服务实现
    Impl-->>Server: 返回业务结果
    Server->>Server: Protobuf 序列化
    Server-->>Stub: HTTP/2 响应
    Stub->>Stub: Protobuf 反序列化
    Stub-->>Client: 返回响应对象

一次 gRPC 调用通常包含以下步骤:

  1. 客户端调用生成的 Stub 方法
  2. Stub 将请求对象序列化为 Protobuf 二进制数据
  3. 请求通过 HTTP/2 发送到服务端
  4. 服务端反序列化请求,并调用对应服务实现
  5. 服务端将业务结果序列化为 Protobuf 二进制数据
  6. 响应通过 HTTP/2 返回给客户端
  7. 客户端 Stub 反序列化响应对象

调用类型

gRPC 支持四种 RPC 模式:

类型 说明 典型场景
Unary RPC 一个请求对应一个响应 普通查询、创建订单、获取用户信息
Server Streaming RPC 一个请求对应服务端连续返回多个响应 下载数据、日志订阅、模型推理流式输出
Client Streaming RPC 客户端连续发送多个请求,服务端返回一个响应 上传文件、批量写入、传感器数据上报
Bidirectional Streaming RPC 客户端和服务端同时以流的方式互相发送消息 聊天、实时协作、长连接控制通道

Unary RPC

rpc GetUser(GetUserRequest) returns (User);

最常见的调用模型,语义接近普通函数调用。

Server Streaming RPC

rpc ListLogs(ListLogsRequest) returns (stream LogEntry);

客户端发送一次请求后,服务端可以持续推送多条响应。适合结果集较大或需要边生成边返回的场景。

Client Streaming RPC

rpc UploadMetrics(stream Metric) returns (UploadSummary);

客户端持续发送消息,服务端在读取完流后返回最终结果。适合批量上传和聚合计算。

Bidirectional Streaming RPC

rpc Chat(stream ChatMessage) returns (stream ChatMessage);

请求流和响应流互相独立,双方都可以持续发送消息。适合实时交互,但也更考验连接管理、背压和超时设计。


Protobuf 定义

在 gRPC 中,.proto 文件是客户端和服务端共同遵守的接口契约。它同时定义两类内容:

  • Service:描述对外暴露的 RPC 方法,包括方法名、请求类型和响应类型
  • Message:描述请求和响应的数据结构,以及每个字段的类型和编号

代码生成器会根据 .proto 文件生成不同语言的客户端 Stub 和服务端接口骨架,业务代码只需要实现对应方法。

示例

syntax = "proto3";

package user.v1;

service UserService {
  rpc GetUser(GetUserRequest) returns (User);
  rpc ListUsers(ListUsersRequest) returns (stream User);
}

message GetUserRequest {
  string id = 1;
}

message ListUsersRequest {
  int32 page_size = 1;
}

message User {
  string id = 1;
  string name = 2;
  string email = 3;
}

字段后面的数字是 field number,是 Protobuf 编码时使用的字段标识。它一旦发布就不应该随意修改,否则可能破坏兼容性。、

字段演进规则

  • 不要修改已有字段的 field number
  • 不要复用已经删除字段的 field number
  • 删除字段时使用 reserved 保留编号和名称
  • 新增字段时保持默认值语义清晰,旧客户端会忽略未知字段;需要区分未设置和默认值时再使用 optional
message User {
  reserved 4, 5;
  reserved "phone";

  string id = 1;
  string name = 2;
  string email = 3;
  string avatar_url = 6;
}

gRPC vs REST

对比项 gRPC REST/JSON
接口定义 .proto 强契约 通常依赖 OpenAPI 或文档
序列化 Protobuf 二进制 JSON 文本
性能 编解码和传输开销较低 可读性好但开销较高
浏览器支持 原生支持有限,通常需要 gRPC-Web 原生友好
流式能力 一等支持 需要 SSE、WebSocket 或 Chunked
调试体验 需要专用工具 curl、浏览器和普通代理都方便
生态定位 服务间通信 公开 HTTP API、前后端交互

常见选择:

  • 内部微服务、高频调用、强类型多语言 SDK:优先考虑 gRPC
  • 面向浏览器、开放平台、需要简单调试:优先考虑 REST/JSON
  • 实时双向交互:可评估 gRPC 双向流或 WebSocket

状态码

gRPC 使用自己的状态码表达 RPC 结果,常见状态如下:

状态码 含义 常见原因
OK 成功 请求正常完成
INVALID_ARGUMENT 参数错误 请求字段非法、格式错误
NOT_FOUND 资源不存在 查询对象不存在
ALREADY_EXISTS 资源已存在 创建重复资源
PERMISSION_DENIED 无权限 身份有效但没有操作权限
UNAUTHENTICATED 未认证 Token 缺失或无效
RESOURCE_EXHAUSTED 资源耗尽 限流、配额不足、消息过大
FAILED_PRECONDITION 前置条件不满足 状态不允许执行当前操作
UNAVAILABLE 服务不可用 服务重启、网络故障、负载均衡无健康节点
DEADLINE_EXCEEDED 超时 请求超过 deadline
INTERNAL 内部错误 服务端未知异常

错误处理建议

对业务可预期错误使用明确状态码,例如 INVALID_ARGUMENTNOT_FOUNDALREADY_EXISTS。对临时故障使用 UNAVAILABLERESOURCE_EXHAUSTED,客户端才更容易做重试和降级。


Deadline、Timeout 与重试

gRPC 推荐客户端为每次调用设置 deadline。它表示请求必须在某个时间点前完成,否则调用会被取消。

deadline = 当前时间 + 最大可接受耗时

设计原则:

  • 每个外部入口请求都应该有总超时预算
  • 下游 gRPC 调用要继承或拆分上游剩余预算
  • 不要在服务端无限等待下游依赖
  • 重试只适合幂等操作或明确支持去重的操作
  • 重试需要配合退避、抖动和最大次数限制

重试风险

重试会放大流量。服务已经过载时,不受控制的重试可能让故障进一步扩大。对写操作尤其要谨慎,需要先确认幂等性。


Metadata、认证与链路追踪

gRPC 的 Metadata 类似 HTTP Header,常用于传递横切信息:

  • 认证信息:authorization
  • 请求标识:x-request-id
  • 租户信息:x-tenant-id
  • 链路追踪上下文:traceparent
  • 灰度或路由标签:x-envx-region

常见做法:

  • 在客户端 Interceptor 里统一注入认证和追踪信息
  • 在服务端 Interceptor 里统一做鉴权、日志和指标采集
  • 不要把大对象塞进 Metadata,业务数据应放在 message body 中
  • 对跨服务透传字段建立白名单,避免泄露内部实现细节

负载均衡

gRPC 基于长连接,负载均衡方式和普通短连接 HTTP API 有差异。

常见模式:

模式 说明 适用场景
Proxy LB 客户端连接代理,由代理转发到后端实例 Kubernetes、Envoy、Ingress、Service Mesh
Client-side LB 客户端感知多个后端地址并自行选择实例 内部服务发现、对性能敏感的服务
DNS LB 通过 DNS 返回多个地址 简单场景,但更新和连接复用要额外关注

Kubernetes 中的长连接倾斜问题

在 Kubernetes 中,如果客户端只和一个 ClusterIP 建立少量长连接,请求可能集中到少数后端 Pod。

更稳定的方式通常是:

  • 使用支持 HTTP/2/gRPC 的代理,如 Envoy、Linkerd、Istio
  • 增加客户端连接数,降低单连接倾斜
  • 使用客户端负载均衡或 headless service
  • 为服务端配置健康检查和优雅下线

生产实践清单

  • 接口设计:以资源和业务动作划分 service,避免把所有 RPC 塞进一个大 service
  • 兼容性:只新增字段,不随意改字段编号、类型和语义
  • 超时控制:所有客户端调用设置 deadline
  • 连接管理:复用 Channel,不要每次请求都新建连接
  • 消息大小:限制最大 request / response size,大结果优先使用流式返回
  • 观测性:记录方法名、状态码、延迟、请求大小、响应大小和 trace id
  • 安全:生产环境使用 TLS/mTLS,并在 Interceptor 中统一鉴权
  • 重试策略:只对明确可重试错误启用重试,并设置退避和最大次数
  • 优雅退出:服务下线前停止接收新请求,等待已有请求完成或超时

常见排障方向

请求超时

  • 检查客户端 deadline 是否过短
  • 检查服务端是否阻塞在下游依赖
  • 查看连接池、线程池、数据库连接池是否耗尽
  • 对比 P50、P95、P99 延迟,判断是否存在长尾问题

UNAVAILABLE

  • 服务端是否重启或滚动发布中
  • 负载均衡器是否支持 HTTP/2
  • 健康检查是否正确
  • DNS 或服务发现结果是否过期
  • 客户端连接是否长期连到已下线实例

消息过大

  • 检查 request / response size 限制
  • 大列表接口改为分页或 server streaming
  • 大文件传输改为分块上传
  • 避免在单个 Protobuf message 中嵌套过深或携带过多重复字段

负载不均

  • 检查客户端是否只建立少量长连接
  • 检查代理是否按连接而不是按请求分发
  • 增加连接数或使用支持 gRPC 的 L7 负载均衡
  • 在 Kubernetes 中评估 headless service、Service Mesh 或客户端负载均衡