This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
2026-05-17 22:00:24 +08:00

6.1 KiB
Raw Permalink Blame History

tags, create time
tags create time
grpc
http2
protocol-stack
multiplexing
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 编译器。

关联笔记