跳转至

深入 HTTP/2 传输原理

gRPC 之所以没有自研全新的私有传输协议,而是坚定地选择 HTTP/2(RFC 7540) 作为底层传输层,是因为 HTTP/2 拥有标准化的多路复用、天然穿透企业级防火墙/代理(通过 80/443 端口)、成熟的 TLS 加密以及健全的生态支持。


1. HTTP/2 核心概念分层

在理解 gRPC 传输机制前,必须厘清 HTTP/2 的四个核心实体:

graph TD
    Conn["1. Connection (物理 TCP 连接)"] --> Stream1["Stream 1 (逻辑双向流)"]
    Conn --> Stream3["Stream 3 (逻辑双向流)"]
    Conn --> Stream5["Stream 5 (逻辑双向流)"]

    Stream1 --> MsgReq["Message (请求消息)"]
    Stream1 --> MsgResp["Message (响应消息)"]

    MsgReq --> FrameH1["HEADERS 帧"]
    MsgReq --> FrameD1["DATA 帧 (分片 1)"]
    MsgReq --> FrameD2["DATA 帧 (分片 2)"]
  1. 连接(Connection):客户端与服务端之间建立的单条物理 TCP 连接。
  2. 流(Stream):在单个连接内并发运行的双向字节流通道。每个 Stream 拥有唯一的 31 位整数 ID:
  3. 客户端发起的 Stream ID 为奇数(1, 3, 5, 7...)。
  4. 服务端发起的 Stream ID 为偶数(2, 4, 6...,在 gRPC 中少用)。
  5. 消息(Message):逻辑上的一个完整 RPC 请求或响应,由一个或多个帧组成。
  6. 帧(Frame):HTTP/2 通信的最小独立单元。

2. HTTP/2 帧结构与关键帧类型

所有 HTTP/2 帧都拥有一个固定的 9 字节通用帧头

+-----------------------------------------------+
|                 Length (24)                   |
+---------------+---------------+---------------+
|   Type (8)    |   Flags (8)   |
+-+-------------+---------------+-------------------------------+
|R|                 Stream Identifier (31)                      |
+=+=============================================================+
|                   Frame Payload (0...)                      ...
+---------------------------------------------------------------+
  • Length (24 bits):帧负载(Payload)的字节长度(默认最大不超过 16,384 字节)。
  • Type (8 bits):帧类型。
  • Flags (8 bits):控制标志位(如 END_STREAM 标识当前流传输结束;END_HEADERS 标识头部接收完毕)。
  • Stream ID (31 bits):指定该帧所属的虚拟流编号。

gRPC 中关键的 HTTP/2 帧类型

帧类型 (Type) gRPC 中的作用
HEADERS 0x1 启动 RPC 调用;传递请求路径、Metadata、以及作为 Trailers 传递状态码
DATA 0x0 承载实际经过 Protobuf 序列化后的二进制 Payload
SETTINGS 0x4 建立连接时协商参数(如 MAX_CONCURRENT_STREAMS、初始流控窗口等)
WINDOW_UPDATE 0x8 HTTP/2 流量控制机制,告知对端可以继续发送多少字节
RST_STREAM 0x3 异常终止或显式取消流(当调用 cancel() 或发生 Deadline 超时时发送)
PING 0x6 测量往返时延(RTT)及 Keepalive 心跳探活
GOAWAY 0x7 优雅停机宣告,通知对端不要再在该连接上新建流

3. HPACK 头部压缩算法

在微服务频繁调用中,HTTP 头部中的 authorizationcontent-type 等字段往往高度重复。HTTP/2 使用 HPACK(RFC 7541) 压缩算法:

graph LR
    H["Header 键值对"] --> StaticTable["静态字典表 (预置 61 个高频头部)"]
    H --> DynamicTable["动态字典表 (连接内自适应学习缓存)"]
    H --> Huffman["霍夫曼编码 (进一步压缩未命中字符)"]
  • 静态表(Static Table):预置了 61 个常用的 HTTP 键值对(例如 :method: POST 在表中编号为 3)。传输时仅需发送索引数值 3(1 个字节),而无需传输 12 字节的字符串。
  • 动态表(Dynamic Table):在连接生命周期内,双方根据已经传递过的自定义 Metadata 动态构建索引。后续相同的 Token 或自定义 Header 仅需传递一个微小的索引编号。

4. 为什么 HTTP/2 的负载均衡需要特殊考量?

传统的 L4(四层网络负载均衡,如 LVS 或普通 TCP 模式的 NGINX)只在 TCP 连接建立时分发流量:

graph TD
    Client["客户端"] --> L4["四层负载均衡器 (TCP Proxy)"]
    L4 -->|长连接一旦建立便不再切换| PodA["服务后端 Pod A (承受 100% 流量)"]
    L4 -.->|无流量| PodB["服务后端 Pod B (闲置)"]
    L4 -.->|无流量| PodC["服务后端 Pod C (闲置)"]

由于 gRPC 在单个 TCP 连接上可以不间断地复用发送成千上万个 RPC,若采用 L4 代理,所有后续请求将全部被倾泻在同一个固定后端节点上,造成集群负载极不均衡。

因此,gRPC 在微服务生产治理中,必须采用: 1. L7 七层负载均衡代理(如 Envoy、Traefik、NGINX gRPC 模块),代理能够解析 HTTP/2 帧并按 RPC 流(Stream)粒度分发; 2. 客户端负载均衡(Client-Side Load Balancing):客户端直接解析后端多个 Pod IP,在本地构建连接池轮询发起 Stream。