HTTP/2 到底比 HTTP/1.1 快在哪?多路复用、头部压缩一次讲透
很多人升级了 HTTP/2,感觉"好像快了,又好像没快"。也有人说 HTTP/2 是"新一代协议,当然更快"。
真相是:HTTP/2 不是无脑快。它快在特定场景,某些情况下甚至不如 HTTP/1.1。今天把原理讲清楚,你就能判断自己的业务到底该不该上。
一、HTTP/1.1 的三个硬伤
先看 HTTP/1.1 为什么慢。
1. 队头阻塞(Head-of-Line Blocking)
HTTP/1.1 一个 TCP 连接,同一时刻只能处理一个请求。发第二个请求,必须等第一个响应回来。
浏览器为了绕开这个限制,会同时开 6 个连接(每个域名)。但连接一多,新问题就来了:TCP 慢启动、拥塞控制的开销被放大好几倍。
更糟的是——如果第一个请求特别慢,后面所有请求全被堵住。这就是"队头阻塞"。
2. 头部冗余
每次请求都要带一堆重复的请求头:Cookie、User-Agent、Accept……几 KB 起步。
一个页面 100 个请求,光头部就传了几百 KB。而这些头部在请求之间高度重复,等于白传。
3. 优先级混乱
多个请求并发时,HTTP/1.1 没有机制告诉服务器"哪个先处理"。结果经常是:关键 CSS 阻塞了渲染,首屏要用的 JS 排在最后才回来。
4. 域名分片带来的额外开销
为了突破 6 连接限制,早期前端会把静态资源拆到多个域名下(domain sharding),靠"每个域名 6 连接"硬堆并发数。
代价是:每个新域名都要多一次 DNS 查询、多一次 TLS 握手。HTTP/2 出来之后,域名分片反而成了负优化——多个域名意味着多个连接,直接把多路复用的收益拆没了。
二、HTTP/2 的四个核心改进
HTTP/2 不是推翻重来,而是在 HTTP 语义不变的前提下,把传输层重新设计了一遍。
1. 二进制分帧层
HTTP/1.1 的报文是纯文本的,解析靠字符串匹配,容易出错、效率也低。
HTTP/2 引入"帧"(Frame)的概念——所有数据(头部、正文)都拆成二进制帧。二进制解析更快,格式也更规整。
每条请求/响应叫一个"流"(Stream),一个流上可以挂多个帧。这是 HTTP/2 多路复用的基础。
2. 多路复用(最核心)
一个 TCP 连接上,可以同时跑多个流。请求 A、B、C 的帧交错发送,互不阻塞。
HTTP/1.1: [请求A]—->[响应A]______[请求B]—->[响应B]
HTTP/2: [A1][B1][A2][C1][B2][A3][C2]…
这样一来:
- 不用再开 6 个连接,一个就够
- HTTP 层的队头阻塞解决了
- 连接少,TCP 慢启动的开销也小了
3. 头部压缩(HPACK)
HTTP/2 用 HPACK 算法压缩头部,核心思路两条:
- 静态表:把常见的头部(:method: GET、user-agent 等)编号存起来,传输只传编号。
- 动态表:连接期间,双方各自维护一张表,记录出现过的头部。第二次出现同样的头部,只传一个索引就行。
效果很直接:重复的头部从几百字节压到几字节。
举个例子,第一次请求完整传:
:method: GET
:path: /index.html
user-agent: Mozilla/5.0 …
accept: text/html
第二次请求只传索引号:
:method: 2 (GET 在静态表里的编号)
:path: 4 (指向动态表上次存的 /index.html)
user-agent: 58 (指向动态表)
accept: 19 (指向静态表)
一个原本上千字节的请求头,第二次只剩几十字节。
4. 流控制与优先级
HTTP/2 支持给流设置优先级和依赖关系,理论上可以告诉服务器"先给我发 CSS"。
不过说实话,这套优先级机制在实际实现里效果一般,各家浏览器后来也各有取舍。这个特性更多是"理论很美"。
三、服务端怎么开 HTTP/2
原理讲完,看落地。HTTP/2 在浏览器里只在 HTTPS 上启用(明文 h2c 基本没人用),所以先把证书配好。
Nginx 一行配置:
listen 443 ssl http2;
要确认 Nginx 编译时带了 –with-http_v2_module(主流发行版的包一般都有)。旧版本要写成 listen 443 ssl; http2 on;,Nginx 1.25.1 之后两种写法都支持,建议用新写法。
客户端这边:
- httpx 支持,httpx.Client(http2=True),前提是装了 h2 库(pip install h2)
- requests 到今天仍然不支持 HTTP/2,想用 HTTP/2 必须换 httpx 或 aiohttp
验证是不是真的开了 HTTP/2:
curl –http2 -I https://your-domain.com
# 看响应头 HTTP/2 200,而不是 HTTP/1.1 200
或者在浏览器 DevTools 的 Network 面板里看 Protocol 列——显示 h2 就对了。
四、实测:HTTP/2 vs HTTP/1.1
光讲原理没感觉,上代码。
用 httpx 分别以 HTTP/1.1 和 HTTP/2 请求同一个服务的 20 个接口:
import httpx
import time
def bench(http2: bool) –> float:
with httpx.Client(http2=http2) as client:
start = time.perf_counter()
for i in range(20):
client.get(f"https://example.com/api/item/{i}")
return time.perf_counter() – start
print("HTTP/1.1:", round(bench(False), 3), "s")
print("HTTP/2: ", round(bench(True), 3), "s")
我这边的测试结果(模拟 30ms 网络延迟):
| 20 个小接口 | ~1.2s | ~0.6s | ~50% |
| 20 个大文件下载 | ~4.5s | ~4.3s | ~4% |
| 单次请求 | ~80ms | ~80ms | 几乎无 |
这个结果我第一次测的时候有点意外——大文件场景几乎没提升。我一度以为是测试方法有问题,换了几个站点才确认:瓶颈真的在带宽,不在协议。
结论很清晰:
- 大量小请求:HTTP/2 优势巨大(多路复用真正生效)
- 少量大文件:差距很小(瓶颈在带宽,不在协议)
- 单次请求:没区别
五、HTTP/2 的坑
坑 1:TCP 层的队头阻塞还在
HTTP/2 解决了 HTTP 层的队头阻塞,但没解决 TCP 层的。
因为 HTTP/2 跑在单个 TCP 连接上。一旦丢包,TCP 要重传,整个连接上所有流都得等那个包重传完。丢包率越高,HTTP/2 反而可能越慢。
这正是 HTTP/3(QUIC)要解决的核心问题——QUIC 基于 UDP,每个流独立处理丢包,一个流卡住不影响其他流。
坑 2:服务器推送基本废了
HTTP/2 的 Server Push 当初设想很美:服务器主动把 CSS/JS 推给客户端,省一次往返。
但实践中问题不少:推的东西如果客户端已经缓存了,就是白白浪费带宽。Chrome 已经在 2022 年移除了 Server Push 支持。所以别指望这个特性。
坑 3:HPACK 动态表并非永远划算
HPACK 的动态表对"大量重复头部"是优化,但对"头部变化频繁"的场景会增加编码开销。不过这个开销通常很小,多数场景可以忽略。
坑 4:连接级流控窗口会被慢流拖累
HTTP/2 除了每个流有窗口,整个连接还有一个连接级流控窗口。如果服务端对某个流处理特别慢,连接级的窗口会被慢慢占满,其他本来很快的流也得等窗口释放。
所以 HTTP/2 不是银弹——服务端处理速度跟不上,多路复用也救不了。真正要治本,还得从服务端响应时间下手。
六、什么时候该上 HTTP/2
该上:
- Web 服务(大量小请求,收益最大)
- API 网关(高频调用,连接复用收益直接)
- 静态资源多的站点
可以不上:
- 纯大文件下载/上传
- 内网低延迟、单请求场景
- 客户端不支持 HTTP/2 的老系统(会 fallback 回 1.1,多一次协商)
七、小结
HTTP/2 快的本质,是用一个连接并发跑多个请求 + 压缩头部。
记住三句话:
三个常见误区
误区一:上了 HTTP/2 就一定快。 大文件下载场景下,提升可以忽略不计。协议不是瓶颈,带宽才是。
误区二:HTTP/2 越多连接越好。 相反,HTTP/2 的最佳实践是收敛域名、减少连接,让多路复用在单连接上充分发挥。
误区三:HTTP/2 能解决所有性能问题。 服务端响应慢、数据库慢、业务逻辑重,换协议一点用没有。协议优化只解决"传输层"的问题。
下一篇聊聊 HTTP/3(QUIC)怎么解决 TCP 队头阻塞,以及它现在到底能不能上生产。
声明:本文为原创技术分享,涉及的采集思路仅供学习与合法用途参考。请遵守目标网站的 robots 协议及相关法律法规,勿将技术用于任何违规场景。



