HTTP/2 多路复用也会堵车?一次搞懂“流冲突”和连接阻塞的排查思路

HTTP/2 的多路复用解决了 HTTP/1.1 的队头阻塞,但并不意味着连接永远不会堵。当多个流(Stream)相互依赖或优先级配置不合理时,仍然可能出现“连接阻塞”。本文用通俗的语言解释流冲突的成因,并提供一套面向小白的排查步骤,帮助你理解是什么让 HTTP/2 又快又稳、什么情况又会让它“卡住”。

HTTP/2 多路复用也会堵车?一次搞懂“流冲突”和连接阻塞的排查思路
封面图:ZuCDN · ZuCDN 原创

折腾Web HTTP时,我发现最麻烦的往往不是安装,而是配置。你在浏览器里打开一个网站,十几个资源几乎同时开始加载——图片、样式表、脚本,不再像 HTTP/1.1 时代那样排队等一个连接。这要感谢 HTTP/2 引入的“多路复用(Multiplexing)”。

但最近有人发现:明明用了 HTTP/2,某些资源却迟迟加载不完,像路上突然堵住一样。这不是幻觉,而是“流冲突”在捣鬼。别担心,今天我们就用最白的大白话,把这个概念讲清楚,再告诉你如果遇到这种情况,应该怎么一步步排查。

从“单车道”到“多车道”:HTTP/2 是怎么变快的?与Web HTTP

实际操作要点

在 HTTP/1.1 时代,一个连接同一时间只能发送一个请求,收到响应后才能发下一个。这就像一条单车道,前车慢了后车就得等——这就是著名的“队头阻塞(Head-of-Line Blocking)”。为了加速,浏览器通常会开 6 个连接,相当于修了 6 条单车道,但每条车道依然只能跑一辆车。

HTTP/2 把车道改成了“多车道并行”——所有请求和响应都在 同一条 TCP 连接 上传输,但被切成多个独立的“流(Stream)”。每个流可以单独发送、暂停、甚至取消,互不等待。这就是 多路复用:一个连接同时承载多个请求和响应,彻底消灭了应用层的队头阻塞。

多路复用真的不会堵吗?——流冲突来了——Web HTTP

容易忽略的细节

理论上多路复用不会产生排队,但实际中 HTTP/2 还有 流优先级(Stream Priority)依赖关系(Dependency) 这个功能。为了让重要资源(比如首屏 CSS、关键 JS)更快到达,浏览器会给每个流设置一个“权重”或“依赖层级”。比如:流 A 依赖流 B 完成,浏览器会先发 B,再发 A。

问题就出在这里:如果依赖关系设计得不够合理(或者服务端不理解优先级),就可能出现 流冲突。比如流 A 等着流 B 的响应,流 B 又在等流 C 的响应,而流 C 的数据包因为丢包需要重传……结果整个连接都卡在等这个重传上。虽然 HTTP/2 允许多个流“并行”,但 TCP 层面是 有序传输 的——丢失的数据包会阻塞后面所有已经到达的数据包,直到重传成功。这种情况就叫做 连接阻塞(Connection Blocking),本质上是 TCP 层的队头阻塞,但被 HTTP/2 的流优先级放大了。

你看,不是多路复用本身堵,而是底层 TCP 的可靠性机制和上层乱设的优先级共同造成的“边缘堵车”。

排查思路:像侦探一样找到“堵点”

假设你的网站使用了 HTTP/2,但用户反馈某个页面加载很慢,或者某些资源(比如一张大图、一个字体文件)始终不出现。我们可以按以下步骤排查。

第一步:确认是否真的使用了 HTTP/2

打开浏览器开发者工具(F12),切换到“网络(Network)”标签。右键点击列标题,勾选“协议”或“Protocol”。如果看到 h2http/2+quic,说明连接确实走 HTTP/2。如果全是 http/1.1,那慢的原因和流冲突无关,需要从其他方向排查。

第二步:观察“Waterfall”瀑布图,找异常

看每个资源的时间线。如果某个资源的“Waiting(TTFB)”非常长,而它前面的资源已经下载完,后面其他资源也在排队,就可能出现了连接阻塞。尤其是当同一个域名下多个请求的“Stalled”时间突然变长,或者“Queueing”超过几十毫秒,就要警惕了。

第三步:检查流优先级设置

浏览器会自动为请求分配优先级,但如果你用自定义脚本(比如 Service Worker 或服务器端推送)改变了预定的顺序,就可能造成冲突。在 Chrome 的“网络”标签里,可以右键导出 .har 文件,然后用在线 HAR 分析工具查看每个请求的 prioritydependents 字段。如果发现某个流依赖了一个进度非常慢的流,可以尝试调整服务器端的资源加载策略,比如把阻塞的图片延后加载(懒加载),或者合并 CSS/JS 以减少并行流数量。

第四步:检查是否存在 TCP 丢包或重传

流冲突的根因往往在 TCP 层面。使用 Chrome 的 chrome://net-internals/#events 可以捕获网络事件,搜索 SOCKET_BYTES_RECEIVEDUDP_RECEIVED,看是否有大量丢包记录。在服务器端,你也可以用 ss -ti 查看每个连接的 TCP 信息,特别是 cwnd(拥塞窗口)是否反复缩小。如果丢包率高,即使 HTTP/2 有很好的多路复用设计,传输速度也会被 TCP 的“有序重传”拖垮。

第五步:考虑升级到 HTTP/3(QUIC)

HTTP/3 基于 QUIC 协议,彻底解决了 TCP 层的队头阻塞——因为 QUIC 使用 UDP,且每个流都是独立传输,丢包只影响那个流,不会阻塞其他流。如果你遇到了由丢包导致的连接阻塞,迁移到 HTTP/3 是根治方案。

如何预防?四招让多路复用流畅

验证与回滚

排查只是事后,更好的做法是提前预防。下面几条建议可以帮你减少流冲突风险:

  • 减少关键依赖:尽量不要让一个资源依赖另一个资源的响应。比如不要用 CSS 里的 @import,它会创建父子依赖关系。
  • 控制并行流数量:默认 HTTP/2 没有并行上限,但过度的并行(比如同时发起 200 个请求)会导致 TCP 拥塞窗口快速填满,反而增加丢包概率。适当地合并请求,或者用 CDN 分散连接。
  • 启用服务器推送要谨慎:HTTP/2 服务器推送(Server Push)容易打乱浏览器原有的优先级排序,导致不需要的资源占用流,阻塞真正需要的资源。目前 Chrome 已默认废弃推送功能,建议关闭。
  • 使用正确的 CDN 和反向代理:确保你的源站、CDN 和反向代理都正确实现了 HTTP/2 优先级,且不会擅自修改优先级表。一些老旧的 Nginx 版本对优先级的支持有 Bug,升级到 1.19+。

总结

容易忽略的细节

HTTP/2 的多路复用很强大,但它不是银弹。当流优先级设计不合理,或者网络底层有丢包时,就会出现“流冲突”导致的连接阻塞。作为普通站长或开发者,你不需要成为 TCP 专家,但掌握上面这些排查步骤——从确认协议、分析瀑布图、检查优先级到观察丢包——就能在页面加载慢时快速定位到真正的问题。最后,如果条件允许,拥抱 HTTP/3 是对未来最好的准备。把这些步骤跑通后,Web HTTP基本就能稳定落地。

延伸阅读