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

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 调用通常包含以下步骤:
- 客户端调用生成的 Stub 方法
- Stub 将请求对象序列化为 Protobuf 二进制数据
- 请求通过 HTTP/2 发送到服务端
- 服务端反序列化请求,并调用对应服务实现
- 服务端将业务结果序列化为 Protobuf 二进制数据
- 响应通过 HTTP/2 返回给客户端
- 客户端 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_ARGUMENT、NOT_FOUND、ALREADY_EXISTS。对临时故障使用 UNAVAILABLE 或 RESOURCE_EXHAUSTED,客户端才更容易做重试和降级。
Deadline、Timeout 与重试
gRPC 推荐客户端为每次调用设置 deadline。它表示请求必须在某个时间点前完成,否则调用会被取消。
deadline = 当前时间 + 最大可接受耗时
设计原则:
- 每个外部入口请求都应该有总超时预算
- 下游 gRPC 调用要继承或拆分上游剩余预算
- 不要在服务端无限等待下游依赖
- 重试只适合幂等操作或明确支持去重的操作
- 重试需要配合退避、抖动和最大次数限制
重试风险
重试会放大流量。服务已经过载时,不受控制的重试可能让故障进一步扩大。对写操作尤其要谨慎,需要先确认幂等性。
Metadata、认证与链路追踪
gRPC 的 Metadata 类似 HTTP Header,常用于传递横切信息:
- 认证信息:
authorization - 请求标识:
x-request-id - 租户信息:
x-tenant-id - 链路追踪上下文:
traceparent - 灰度或路由标签:
x-env、x-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 或客户端负载均衡