流量控制

连接级与流级窗口、WINDOW_UPDATE 语义与初始窗口变更的边界情况。

作者 David更新于 第 6 篇(共 16 篇)

为什么需要

1.1 完全依赖 TCP 的流量控制:接收方读得慢,TCP 窗口收缩,发送方自然被限速。

h2 打破了这个假设——一条 TCP 连接上有几十个流。若只靠 TCP,一个慢消费者(比如客户端把某个大下载的数据攒在内存里没处理)会让整条连接的 TCP 窗口关闭,把其他所有流一起憋死。

于是 h2 在 HTTP 层重新实现了一套流量控制,粒度到单个流。

两级窗口

每个方向上维护两套窗口,同一个 DATA 帧同时消耗两者:

级别作用范围由谁计
连接级整条连接(流 ID = 0)全部 DATA 帧之和
流级单个流该流的 DATA 帧

发送一个 N 字节载荷的 DATA 帧:连接窗口 −N,且该流窗口 −N。两者都必须 ≥ N 才允许发送——任一为 0 即阻塞。

关键限制:只有 DATA 帧受流量控制。HEADERS、SETTINGS、RST_STREAM、PING 等控制帧不受限,保证控制信令永远能通过(否则会死锁)。这也意味着巨大的头部无法被流量控制约束,需靠 MAX_HEADER_LIST_SIZE 单独限制。

窗口参数

项值
初始窗口(两级)65535 字节(2^16 − 1)
流级初始值可调SETTINGS_INITIAL_WINDOW_SIZE
连接级初始值不可用 SETTINGS 调整,只能靠 WINDOW_UPDATE 增大
窗口上限2^31 − 1

默认 65535 字节在高带宽延迟积(BDP)链路上是严重瓶颈:跨洋 100ms RTT 下,单流吞吐上限约 65535 / 0.1s ≈ 640 KB/s。生产部署几乎都要调大(常见 1–16 MB)——这是 h2 调优的头号参数。

WINDOW_UPDATE

+-+-------------------------------------------------------------+
|R|              Window Size Increment (31)                     |
+-+-------------------------------------------------------------+
  • 载荷 4 字节,其中 1 位保留 + 31 位增量(不是新的绝对值)。
  • 流 ID = 0 → 增连接级窗口;非零 → 增该流窗口。
  • 增量为 0 是错误:流级 → PROTOCOL_ERROR 的 RST_STREAM;连接级 → 连接错误。
  • 累加后超过 2^31 − 1 → FLOW_CONTROL_ERROR。
  • 即使流已进入 half-closed/closed,仍可能收到对端发来的 WINDOW_UPDATE(竞态),MUST 容忍。

接收方通常在把数据交给应用层后立即发 WINDOW_UPDATE 补足窗口。

INITIAL_WINDOW_SIZE 变更的坑

SETTINGS_INITIAL_WINDOW_SIZE 改变时,已存在的所有流的窗口要按差值调整:

新流级窗口 = 当前窗口 + (新 INITIAL_WINDOW_SIZE - 旧 INITIAL_WINDOW_SIZE)

两个必须注意的后果:

  1. 窗口可以变成负数。若已发送 60000 字节、随后对端把初始窗口从 65535 调到 16384,则窗口变为 5535 + (16384 − 65535) = −43616。此时发送方 MUST 停止发送,直到 WINDOW_UPDATE 把它拉回正数。负窗口是合法状态,不是错误。
  2. 该设置只影响流级,连接级窗口不受影响。

死锁风险

流量控制是自己实现的,就有自己造死锁的可能:

  • 接收方必须及时发 WINDOW_UPDATE,否则发送方永久阻塞。
  • 一个不读数据的应用会让窗口耗尽——服务端应设读超时,必要时 RST_STREAM。
  • 双方都在等对方的窗口时(如双向大流量的 gRPC 流式调用),实现必须保证控制帧优先发送。

与 TCP 流量控制的关系

两层同时生效,取更严格者:

应用可发送量 = min(h2 连接窗口, h2 流窗口, TCP 拥塞窗口, TCP 接收窗口)

h2 窗口调得再大也突破不了 TCP 的限制;反之 TCP 再快,h2 窗口没补上也发不出去。调优时两者都要看。

HTTP/3 的对照

HTTP/3 删除了这一层——流量控制下沉到 QUIC 传输层:

h2h3 / QUIC
WINDOW_UPDATE(流级)QUIC MAX_STREAM_DATA 帧
WINDOW_UPDATE(连接级)QUIC MAX_DATA 帧
SETTINGS_INITIAL_WINDOW_SIZEQUIC 传输参数 initial_max_stream_data_*
还额外受 TCP 窗口约束QUIC 自己就是传输层,只有一层

这消除了 h2「HTTP 层与 TCP 层两套流控互相打架」的复杂性——详见 QUIC 传输层。

下一篇:SETTINGS 与优先级。

本页目录