Files
cs-note/hzh/MS/06-gRPC/01-协议与架构.md
T

162 lines
6.1 KiB
Markdown
Raw Normal View History

2026-05-24 11:42:38 +08:00
---
tags: [grpc, http2, protocol-stack, multiplexing]
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 整体架构
```mermaid
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()` 超时来避免启动时的无限等待:
>
> ```go
> 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,解析快 | 双方不需要手写解析逻辑 |
### 多路复用演示
```mermaid
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 在高频内部调用的性能优势来源。
## 协议栈层次
```mermaid
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-拦截器]] — 切面编程和上下文传播