~/ ?.log $
返回文章列表
13 min read
更新于 2026年3月6日

我不知道的HTTP(05)— 从HTTP/1.1到HTTP/3:队头阻塞是如何被消灭的

HTTP 协议从 1.1 到 2 再到 3,每一次大版本升级都在解决同一个核心问题——队头阻塞(Head-of-Line Blocking)。

一、问题引入

HTTP 协议从 1.1 到 2 再到 3,每一次大版本升级都在解决同一个核心问题——队头阻塞(Head-of-Line Blocking)。

很多人以为 HTTP/2 的多路复用已经彻底解决了队头阻塞,但实际上,它只解了一半。另一半藏在 TCP 层,直到 HTTP/3 用一个看似激进的决定——换掉 TCP——才把它真正消灭。这篇文章从队头阻塞出发,串起三代 HTTP 协议的核心演进逻辑。

二、表面认知:大多数人停在这里

大多数开发者对 HTTP 协议演进的理解是这样的:HTTP/1.1 太慢,HTTP/2 加了多路复用变快了,HTTP/3 换成 QUIC 更快了。

这个描述不算错,但太粗了。如果追问一句”HTTP/2 的多路复用到底解决了什么层面的问题?还有什么没解决?“,大部分人就答不上来了。

问题的关键在于——队头阻塞不是一个问题,而是两个:一个在应用层,一个在传输层。HTTP/2 只干掉了前者。

三、往下挖:队头阻塞的两次猎杀

3.1 HTTP/1.1 的队头阻塞:排队等前面的人

HTTP/1.1 的规范要求,一条 TCP 连接上的请求必须排队处理——前一个响应没回来,后面的请求只能干等着。一个加载缓慢的大图片会卡住同一连接上的 CSS 和 JavaScript 文件,即使服务器早就准备好了后者的响应。

浏览器的应对策略是对同一域名开 6 条并行 TCP 连接。但连接数是有上限的,每条连接都要经历 TCP 握手和 TLS 握手的开销。这不是真正的解决方案,更像是在绕弯路。

说白了,HTTP/1.1 的队头阻塞就是”单车道公路上,前面的卡车不动,后面的跑车也只能等着”。浏览器多开连接相当于修了 6 条车道,但车道数终究有限。

3.2 HTTP/2 的解法:一条连接、多条虚拟车道

HTTP/2 引入了二进制分帧和流(Stream)的概念。所有请求和响应被拆成一个个带有 Stream ID 的帧(Frame),在同一条 TCP 连接上交错传输。接收端根据 Stream ID 把帧重新组装成完整的消息。

这里有一个很多人会忽略的细节——HTTP/2 的”流”并不是真实的物理通道,而是逻辑上的标记。数据帧在同一条 TCP 连接的字节流里混在一起传输,只靠帧头部的 Stream ID 来区分归属。HTML 的帧、CSS 的帧、JavaScript 的帧可以交替发送,谁的数据先准备好就先发谁的。

这就是多路复用。它解决了应用层的队头阻塞:不再需要排队等前一个请求完成,多个请求可以在同一连接上真正并行。配合 HPACK 头部压缩(相同的 User-AgentCookie 等字段只传一次),HTTP/2 在大多数场景下比 HTTP/1.1 快了 20%-30%。

3.3 被忽视的另一半:TCP 层的队头阻塞

HTTP/2 解决了应用层的问题,但它运行在 TCP 之上,而 TCP 有一个刚性约束:数据必须按序交付

假设一条 TCP 连接上同时传输 3 个 HTTP/2 流的数据帧。如果中间某个 TCP 报文段丢了,即使后续的报文段已经到达接收端,TCP 也不会把它们交给上层——它会暂停一切,等丢失的那个包重传回来,重新排好序再统一交付。

换句话说,HTTP/2 多路复用和 HTTP/1.1 管线化的根本区别在于:HTTP/2 在应用层面消除了排队等待,但在 TCP 层面,所有流共享同一条字节流,一个包丢失会冻结所有流。在高丢包率的网络环境下(如移动网络),HTTP/2 甚至可能比 HTTP/1.1 的 6 条独立连接更慢,因为 HTTP/1.1 的 6 条连接中某条丢包只影响那一条,不会牵连其他连接上的请求。

这就是 HTTP/2 的”隐性代价”。

3.4 为什么不修 TCP,而是换掉它

既然问题出在 TCP 的按序交付机制上,为什么不直接改 TCP?

因为 TCP 协议栈内置在操作系统内核中。Windows、Linux、macOS、iOS、Android,全球数十亿设备的内核里都跑着 TCP。修改 TCP 意味着需要所有操作系统厂商同时升级内核,所有中间设备(路由器、防火墙、NAT 网关)同步更新——这在工程上几乎不可能。这个困境有个专门的名字:协议僵化(Protocol Ossification)

说白了,TCP 不是不想改,是改不动。它太成功了,成功到全世界都依赖它,反过来也就没人敢动它。

所以 HTTP/3 选择了另一条路:在 UDP 之上重新造一个传输协议——QUIC。UDP 本身不保证可靠性和顺序,QUIC 在用户态(应用层)自己实现了可靠传输、拥塞控制和加密,绕过了内核的限制。

3.5 HTTP/3 + QUIC:流独立的传输层

QUIC 和 TCP 最关键的区别在于:流在传输层就是独立的

在 TCP 上,所有 HTTP/2 流的数据混在一条字节流里。在 QUIC 上,每条流有自己独立的传输队列和重传机制。Stream A 丢了一个包,只有 Stream A 暂停等重传,Stream B 和 Stream C 完全不受影响。

如果你只记住一句话,记住这个:HTTP/2 的流是应用层的逻辑概念,底层共享一条 TCP 字节流;QUIC 的流是传输层原生支持的,底层真正独立。这才是队头阻塞被彻底消灭的原因。

除了流独立性,QUIC 还带来了两个重要改进。

(1) 握手更快。QUIC 将 TLS 1.3 握手和传输层握手合并为一步,新连接只需 1-RTT,复用连接甚至可以 0-RTT 直接发数据。对比 TCP + TLS 1.3 至少需要 2-RTT(TCP 握手 1-RTT + TLS 握手 1-RTT),省下的这一个往返在高延迟网络中差异显著。

(2) 连接迁移。TCP 用四元组(源 IP、源端口、目标 IP、目标端口)标识连接,手机从 Wi-Fi 切到 4G 时 IP 变了,TCP 连接直接断开,必须重新握手。QUIC 用一个 Connection ID 标识连接,网络切换后只要 Connection ID 不变,连接继续有效。

3.6 传统优化手段为什么在 HTTP/2 下反而变慢了

这里有一个很多人会忽略的细节——HTTP/1.1 时代积累的一些性能优化技巧,在 HTTP/2 下不仅没用,反而有害。

比如精灵图(CSS Sprites)。在 HTTP/1.1 下,把 20 个小图标合并成一张大图可以减少 19 次连接开销,确实有效。但在 HTTP/2 下,20 个小图标可以通过多路复用在一条连接上并行传输,合并反而造成了问题:只要其中一个图标更新,整张大图的缓存就失效了,用户需要重新下载全部 20 个图标的数据。

再比如域名分片(Domain Sharding)。HTTP/1.1 下为了突破浏览器对单域名 6 条连接的限制,开发者会把资源分散到 img1.example.comimg2.example.com 等多个子域名。在 HTTP/2 下,一条连接就能处理所有请求,分片反而迫使浏览器对每个子域名单独建立 TCP + TLS 连接,多出来的握手开销白白浪费。

资源内联(Inlining)也一样。把 CSS 直接写进 HTML 的 <style> 标签里,在 HTTP/1.1 下省了一次请求。但内联的 CSS 无法被浏览器独立缓存——每次 HTML 更新,内联的 CSS 即使没有变化也要重新下载。HTTP/2 下,一个独立的 CSS 文件通过多路复用加载几乎没有额外延迟,还能享受独立缓存。

四、这意味着什么:现在可以验证了

HTTP/3 不再是”未来技术”。截至 2025 年 10 月,全球约 35% 的 Web 流量已经运行在 HTTP/3 上(Cloudflare 数据)。Chrome 87+、Firefox 88+、Safari 18.2+ 均已完整支持。Google、YouTube、Facebook 等大型站点早已默认启用。

可以用 curl 直接验证当前使用的协议版本。下面两条命令分别强制使用 HTTP/2 和 HTTP/3 请求同一个地址,-w 参数打印实际使用的协议和握手耗时:

curl -s -o /dev/null -w "protocol: %{http_version}\ntime_connect: %{time_connect}s\ntime_appconnect: %{time_appconnect}s\ntime_total: %{time_total}s\n" --http2 https://www.cloudflare.com

curl -s -o /dev/null -w "protocol: %{http_version}\ntime_connect: %{time_connect}s\ntime_appconnect: %{time_appconnect}s\ntime_total: %{time_total}s\n" --http3 https://www.cloudflare.com

对比输出中的 time_appconnect(握手完成时间)和 time_total(总耗时),在高延迟网络下通常能观察到 HTTP/3 的握手阶段明显更短。http_version 字段会显示 23,确认协议确实生效。

如果在 Chrome 中查看,打开 DevTools 的 Network 面板,右键表头勾选 Protocol 列。HTTP/2 请求显示 h2,HTTP/3 请求显示 h3。大多数现代网站在同一个页面里可能同时存在 h2h3 的请求——这取决于服务端是否在响应头中通过 Alt-Svc 字段告知浏览器支持 HTTP/3,以及浏览器是否在后续请求中完成了协议升级。

五、总结

队头阻塞经历了三个阶段:HTTP/1.1 在应用层排队,HTTP/2 解决了应用层但暴露了 TCP 层,HTTP/3 换掉 TCP 彻底消灭了它。协议演进的主线,就是这条”消灭队头阻塞”的路径。


本系列其他文章:

相关主题:

  • HTTP/1.1 的 TCP 三次握手和 TLS 握手细节,见本系列第 01 篇和第 04 篇
  • 浏览器如何利用多进程架构处理网络请求?可以看浏览器系列:进程、线程与多进程架构
share.ts

// 觉得这篇文章有帮助?

// 欢迎分享给更多人

export const  subscribe  =  "/rss.xml" ;