什么是队头阻塞
食堂只有一个窗口,第一个人点了十斤米饭,后面所有人干等——哪怕他们只要一碗汤。HTTP/1.1 的 keepalive 连接就是这样:请求按序发送、响应按序返回,前面任何一个慢了(大图、慢接口),同连接上后面的小请求全部被堵。队头阻塞的要害不是平均速度,是尾延迟——关键接口被无关大响应连坐,P99 直接爆表。
第一代解法:多开窗口
既然一条队堵,就多排几条队——浏览器对同一域名并发开 6 条 TCP 连接。官方管道化(pipelining,一条连接上连发多个请求)因为响应必须严格按序,快的人必须等慢的人,从未被浏览器启用。6 条不够?行业发明了域名分片:把静态资源散到 img.example.com、static.example.com……骗浏览器开更多连接。这套魔改一时爽,代价是:每条连接重新握手慢启动、TLS 多份、缓存分家,后来 HTTP/2 时代成了反模式——连接越少越好。
HTTP/2:应用层的队头治好了
HTTP/2 把报文拆成二进制帧,一条连接上多个 stream(流)交错传输,每个 stream 独立:慢响应的帧晚点到,不影响别的 stream 的帧先到。应用层排队没了,六连接上限、域名分片一并扫进历史。细节下一篇展开。
但是:TCP 层还有一个
一条 HTTP/2 连接 = 一条 TCP 连接
TCP 只认字节流,不认 stream:
第 5 号包丢了 → 第 6、7、8 号包即使先到也得在内核里干等
——它们可能分属 stream A、B、C
三个 stream 一起被一个丢包拖累
所有请求挤一条 TCP 连接后,
丢包的影响面反而比 HTTP/1.1 的 6 条连接更大:
丢包率 2%、RTT 100ms 时,HTTP/2 可能比 6 连接的 HTTP/1.1 更慢这就是传输层队头阻塞:TCP 的可靠交付语义(按序、不丢)和应用的多路复用诉求天然打架。治本方案只有换掉 TCP——那是 QUIC 和 HTTP/3 的故事(第十九篇)。
各代方案对比
| 方案 | 应用层队头 | 传输层队头 | 副作用 |
|---|---|---|---|
| HTTP/1.1 串行 | 有 | 基本无(一条一请求) | 吞吐差 |
| 多连接/域名分片 | 稀释 | 稀释 | 握手多、慢启动重复 |
| HTTP/2 | 消除 | 存在且放大 | 单连接丢包连坐 |
| HTTP/3 (QUIC) | 消除 | 消除 | UDP 部署门槛 |
下一篇正式进入 HTTP/2:二进制分帧、多路复用、头部压缩 HPACK——它解决了什么,又留下了什么。
评论 (0)