6.1 KiB
tags, create time
| tags | create time | ||||
|---|---|---|---|---|---|
|
2026-05-07 16:00 |
gRPC 协议与架构
概述
本文深入讲解 gRPC 的内部架构和 HTTP/2 协议栈层次。理解这些底层机制后,才能解释「为什么 gRPC 比 HTTP/1.1 REST 更快」、「连接为什么不会泄漏」——不再停留在"听说性能好"的模糊认知层面。
[!question] 先想一个问题
微服务之间每秒可能产生数万到数十万次 RPC 调用。如果每次调用都新开一条 TCP 连接,操作系统会耗尽哪些资源?
TCP 握手需要三次交互、端口有数量上限(单进程约 65535)、内核要为每个 socket 维护内存——当并发连接数达到万级时,CPU 花在建立和拆除连接上的时间甚至会超过处理业务的时间。这就是 gRPC 默认复用连接的设计动机。
gRPC 整体架构
graph TB
subgraph Client["客户端应用"]
CApp["业务代码"]
CStub["Generated Stub"]
CChan["gRPC Channel"]
CCall["Client Call"]
CApp --> CStub
CStub --> CChan
CChan --> CCall
end
subgraph Network["网络层"]
H2["HTTP/2 Frame Layer"]
HP["HPACK Header Compression"]
LM["Load Balancing Picker"]
NR["Name Resolver"]
CCall --> LM
LM --> H2
H2 --> HP
end
subgraph Server["服务端应用"]
SChan["Server Listener"]
SH2["HTTP/2 Frame Layer"]
SHP["HPACK Header Compression"]
SH2 --> SHP
Handler["Registered Handler"]
SHandler["业务代码"]
SHP --> Handler
Handler --> SHandler
end
CChan <-->|Binary Frames| SChan
关键组件说明
| 组件 | 职责 |
|---|---|
| Generated Stub | 从 .proto 文件编译生成的桩代码,封装了序列化 / 反序列化和网络通信细节 |
| gRPC Channel | 逻辑连接抽象,内部管理真实 TCP 连接的创建、复用和健康检查 |
| HTTP/2 Frame Layer | 二进制分帧层,将所有数据拆分为轻量级的 Frame 传输 |
| HPACK | 头部压缩算法,避免重复传输相同的 metadata 字段 |
[!tip] Go 实现细节
Go 中每个 gRPC Channel 底层维护一个 transport 连接池,由负载均衡器动态分配 SubConn。你可以显式设置
WithBlock()超时来避免启动时的无限等待:conn, err := grpc.DialContext(ctx, target, grpc.WithBlock(), grpc.WithTimeout(5*time.Second), )
HTTP/2 的关键特性
gRPC 不是一种新协议,而是 Protocol Buffers + HTTP/2 的绑定规范。HTTP/2 为 gRPC 提供了三个核心能力:
| 特性 | HTTP/1.1 | HTTP/2 | gRPC 收益 |
|---|---|---|---|
| 多路复用 | 一个连接只能处理一个请求(除非 SPDY) | 一个 TCP 连接上并行的多个 Stream | 无需连接池,连接复用率极高 |
| 头部压缩 | 明文 Head,重复字段多 | HPACK 算法压缩 | 减小传输体积,降低延迟 |
| 二进制分帧 | 文本协议,解析慢 | 二进制 Frame,解析快 | 双方不需要手写解析逻辑 |
多路复用演示
sequenceDiagram
participant C as Client
participant H2 as HTTP/2 Connection
participant S as Server
Note over C,S: 一个 TCP 连接,四个并发 Stream
C->>H2: Stream 1: GetOrder(id=1)
S->>H2: Stream 1: Order{...}
C->>H2: Stream 2: GetUser(id=5)
S->>H2: Stream 2: User{...}
C->>H2: Stream 3: CreateItem(...)
S->>H2: Stream 3: Item{id: "new"}
[!keypoint] 关键洞察
HTTP/1.1 开 10 个并行请求需要 10 条 TCP 连接 → 握手开销大、端口耗尽。一条 HTTP/2 连接就能承载几百个并发 RPC,这就是 gRPC 在高频内部调用的性能优势来源。
协议栈层次
graph LR
App["Application<br/>Business Logic"] --> Stub["Generated Stub"]
Stub --> GRPC["gRPC Framework"]
GRPC --> H2["HTTP/2 Protocol"]
H2 --> TCP["TCP/IP"]
style App fill:#e3f2fd
style Stub fill:#fff3e0
style GRPC fill:#c8e6c9
style H2 fill:#fce4ec
style TCP fill:#f3e5f5
每一层解决不同的问题:
| 层级 | 解决的问题 | 类比 |
|---|---|---|
| Application | 你写什么业务逻辑 | 写信的内容 |
| Generated Stub | 把业务对象映射为二进制编码 | 翻译官(Proto 定义 = 字典) |
| gRPC Framework | 负责重试、拦截器、流控 | 邮局分拣系统 |
| HTTP/2 | 多路复用 + 头部压缩 + 二进制帧 | 快递包裹的分装规范 |
| TCP/IP | 可靠传输 + 路由寻址 | 公路运输网络 |
为什么不只是"更快的 HTTP"
许多开发者误以为 gRPC 的优势只是"用了更快的序列化"。实际上真正的分水岭在于:
[!summary] gRPC vs HTTP API 的本质差异
维度 HTTP API(REST) gRPC 契约先行 接口文档滞后于代码 .proto是单一事实来源类型安全 JSON 无类型,运行时才暴露 bug 编译期捕获字段缺失、类型错误 代码即 SDK 客户端需要手动拼装 HTTP 请求 自动生成全语言客户端 Stub 连接复用 需要自行管理连接池 框架内置连接复用和负载均衡 流式能力 WebSocket 需额外建通道 原生支持双向流,强类型契约
[!question] 带着问题继续读
既然 gRPC 这么多优势,是不是所有场景都应该用 gRPC?什么情况下 HTTP/JSON 仍然更合适?
答案见 02-服务治理/05-服务间通信。对外暴露 API 时,HTTP/JSON 仍然不可替代——因为浏览器的 Native Fetch 无法直接调用 gRPC,第三方接入者也不想安装 Proto 编译器。
关联笔记
- 02-服务治理/05-服务间通信 — gRPC 与 REST 的基础对比及选型建议
- 02-服务治理/06-容错模式 — 基于此架构的重试、熔断等治理机制
- 02-Proto设计 — Proto 文件设计的进阶实践
- 03-RPC模式 — 四种 RPC 模式的深度用法
- 04-拦截器 — 切面编程和上下文传播