vault backup: 2026-05-07 15:55:02
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
---
|
||||
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-服务治理/服务间通信]]。对外暴露 API 时,HTTP/JSON 仍然不可替代——因为浏览器的 Native Fetch 无法直接调用 gRPC,第三方接入者也不想安装 Proto 编译器。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[02-服务治理/服务间通信]] — gRPC 与 REST 的基础对比及选型建议
|
||||
- [[02-服务治理/容错模式/README]] — 基于此架构的重试、熔断等治理机制
|
||||
- [[02-Proto设计]] — Proto 文件设计的进阶实践
|
||||
- [[03-RPC模式]] — 四种 RPC 模式的深度用法
|
||||
- [[04-拦截器]] — 切面编程和上下文传播
|
||||
Reference in New Issue
Block a user