bigywater.tech
gRPC · 10

HTTP/2 流控窗口:初始窗口与 BDP 估计怎么影响吞吐

gRPC 一条连接能跑多快?HTTP/2 的流控窗口怎么限制吞吐?动态窗口是什么?多个流共享一条连接时又怎样?这篇用抓包代理人为加延迟,量窗口、RTT 和吞吐的关系。

2.5 对 119.5 MiB/s
RTT 20 ms 时静态窗口 64 KiB 与 4 MiB 的吞吐
9.1 对 402.7 MiB/s
4 个流共享一条连接,连接窗口 64 KiB 与 4 MiB 的总吞吐(流窗口都是 1 MiB)
窗口 / RTT
吞吐的上限:发满一个窗口之后要等 WINDOW_UPDATE

窗口除以往返时间

实测 · 本机执行输出
  客户端设置                    吞吐 MiB/s     窗口/RTT 的理论值    
  默认(动态窗口,BDP 估计)          642.6        —             
  静态窗口 64 KiB              336.5        —             
  静态窗口 256 KiB             797.4        —             
  静态窗口 1 MiB               957.7        —             
  静态窗口 4 MiB               1339.0       —             

发送方每发满一个窗口就要等对方放出新窗口,所以吞吐上限是窗口除以 RTT。RTT 5 ms、窗口 64 KiB 的上限是 12.5 MiB/s,实测 9.2;RTT 20 ms 降到 2.5。

延迟越大越要大窗口

实测 · 本机执行输出
  客户端设置                    吞吐 MiB/s     窗口/RTT 的理论值    
  默认(动态窗口,BDP 估计)          144.6        —             
  静态窗口 64 KiB              9.2          12.5          
  静态窗口 256 KiB             33.4         50.0          
  静态窗口 1 MiB               124.4        200.0         
  静态窗口 4 MiB               435.8        800.0         

回环上 64 KiB 和 4 MiB 窗口只差 4 倍,RTT 20 ms 时差约 48 倍。跨机房、跨地域的流式传输,默认的小窗口会成为瓶颈。

动态窗口

实测 · 本机执行输出
  客户端设置                    吞吐 MiB/s     窗口/RTT 的理论值    
  默认(动态窗口,BDP 估计)          23.4         —             
  静态窗口 64 KiB              2.5          3.1           
  静态窗口 256 KiB             9.0          12.5          
  静态窗口 1 MiB               31.9         50.0          
  静态窗口 4 MiB               119.5        200.0         

grpc-go 默认用 BDP 估计器:通过 PING 测量,逐步放大窗口(WINDOW_UPDATE 增量从 16384 慢慢变大)。代价是传输开始时窗口小,短传输来不及放大。显式设 WithInitialWindowSize 则关闭它(第 8 篇)。

两层窗口

实测 · 本机执行输出
  吞吐 22.7 MiB/s;连接级 WINDOW_UPDATE 共 56 个,前 10 个的增量(字节):[16384 16384 16384 16383 65535 16384 32768 32767 32768 98304];客户端发出的 PING 16 个
  默认(动态窗口,BDP 估计)          客户端 SETTINGS:""
  静态窗口 64 KiB              客户端 SETTINGS:"INITIAL_WINDOW_SIZE=65536"
  静态窗口 1 MiB               客户端 SETTINGS:"INITIAL_WINDOW_SIZE=1048576"

HTTP/2 每个流一个窗口、整条连接一个窗口。多个流共享连接时,连接窗口过小会卡住所有流:流窗口 1 MiB、连接窗口 64 KiB 时 4 个流总共只有 9.1 MiB/s。

小消息

实测 · 本机执行输出
  连接窗口 65536    字节:4 × 4 MiB 共 16 MiB,用时 1765.9 ms,总吞吐 9.1 MiB/s
  连接窗口 4194304  字节:4 × 4 MiB 共 16 MiB,用时 39.7 ms,总吞吐 402.7 MiB/s

小消息只要一个往返,不受窗口影响:RTT 5 ms 下两种窗口都是约 5.8 ms/次。

怎么选

实测 · 本机执行输出
  默认(动态窗口,BDP 估计)          200 次 Unary 平均 5.87 ms/次
  静态窗口 64 KiB              200 次 Unary 平均 5.83 ms/次

一般小消息 RPC 保持默认;大量数据的流式传输、跨高延迟链路,考虑调大窗口并留意连接级窗口(见第 36 篇压测);改了就失去动态调整。

分步演示

流控窗口
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1 / grpc-go v1.84.0 / protobuf-go v1.36.12 / golang.org/x/net v0.57.0 / protoc 36.2(Homebrew);服务端和客户端都是本机起的 Go 进程(127.0.0.1,端口 9800 以上,不访问任何外部服务);需要看线上字节时用我自己写的 TCP 抓包代理加 golang.org/x/net/http2 的 Framer 解析 HTTP/2 帧;计时用 time.Now 或 perf_counter,数字每次运行会有小幅抖动。延迟由抓包代理的“延迟线”实现(每个数据块晚到指定时间,不累加):RTT = 两个方向延迟之和;理论值 = 窗口 / RTT;吞吐按 256 KiB × 条数 / 总用时计;每个配置只测一次(同一台机器上重复运行,数字会有 ±10% 的波动,例如第一次运行 RTT 5 ms 的 64 KiB 窗口是 9.0、再次运行是 9.2)
    • 没有验证:真实网络上的丢包与拥塞控制(TCP 本身的窗口也会限制);更大的 RTT(100 ms 以上);BDP 估计器内部什么时候放大窗口的具体规则(只看了 WINDOW_UPDATE 增量的序列);服务端侧窗口(WithInitialWindowSize 的 ServerOption)对客户端上传速度的影响;多连接并行传输是否比一条连接大窗口更好
    京ICP备2026067502号-1京公网安备11010502063487号