HTTP/2 的三大招:一条连接上并发多个流(多路复用),每个流有自己的流量控制窗口,头部用 HPACK 压缩。这篇在本机起一个 HTTP/2 服务器,用 netlab 造 100 ms 的往返,逐个量:并发到底省多少、窗口小了会怎样、动态表压缩了多少。
HTTP/1.1 一条连接顺序发:4143 ms(≈ 握手 1 RTT + 10 × (1 RTT + 300 ms) = 100 + 10 × 400 = 4100 ms) HTTP/1.1 6 条并行连接:913 ms(10 个请求分两批:6 + 4,每批 1 RTT + 300 ms,加握手 100 ms ≈ 900 ms) HTTP/2 一条连接:814 ms(含 TCP 握手 1 RTT + TLS 握手 1 RTT + 10 个流同时 1 RTT + 300 ms ≈ 600 ms);10 个流完成的时刻(ms,从发出请求起):[503, 503, 503, 503, 503, 503, 503, 603, 603, 604]
10 个请求一条连接并发,总耗时 814 ms,接近 6 条 HTTP/1.1 连接(913 ms);顺序发需要 4143 ms。
不确认时:服务器发出 65535 字节(4 个 DATA 帧)就停了(初始流窗口 65535、连接窗口 65535;停了 1.5 秒也不动)
补发 WINDOW_UPDATE(流 + 连接各 +1 MiB)后:又过 13 ms 传完,总共 1048576 字节
窗口大小决定吞吐:往返 100 ms 时,下载 4 MiB,客户端宣告不同的初始流窗口(连接窗口放到 16 MiB)的耗时
初始窗口 65535 字节:13.09 秒, 0.32 MB/s(窗口 ÷ RTT = 0.66 MB/s)
初始窗口 262144 字节: 3.37 秒, 1.24 MB/s(窗口 ÷ RTT = 2.62 MB/s)
初始窗口 1048576 字节: 1.11 秒, 3.79 MB/s(窗口 ÷ RTT = 10.49 MB/s)
初始窗口 4194304 字节: 0.71 秒, 5.93 MB/s(窗口 ÷ RTT = 41.94 MB/s)窗口用完发送方就停;WINDOW_UPDATE 才能让它继续。初始窗口越小下载越慢,但实测吞吐低于窗口 ÷ RTT。
10 个相同请求的头部块大小(字节):[210, 8, 8, 8, 8, 8, 8, 8, 8, 8];同样内容用 HTTP/1.1 文本写要 398 字节左右(每个头部名字 + ": " + 值 + CRLF) 第 1 个 210 字节(新头部要写出来,同时放进动态表),第 2 个起 8 字节(只是引用动态表的索引),压缩到 2.0% 把动态表关掉(HEADER_TABLE_SIZE = 0):[211, 210, 210]——每次都得重新写出来(只剩霍夫曼编码和静态表的压缩) 只改一个头部的值(换一个 200 字节的 Cookie):下一个请求的头部块 160 字节(新的 Cookie 要整个重发,其余仍是引用) 对比真实服务器:对 example.com 连续在同一个连接上发 3 个请求,看响应 HEADERS 帧的头部块大小 example.com 三次响应的头部(解码后名字+值总字节):[243, 243, 243](线上 HPACK 编码的字节数在上一篇里看到过是 160 字节左右;解码后的原始大小在这里)
动态表让重复的头部只需要 8 字节的引用;关掉动态表则每次 210 字节。