抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

HTTP/2 于 2015 年发布,是 HTTP 协议自 1999 年 HTTP/1.1 以来的首次重大更新。本文将详细介绍 HTTP/2 相比 HTTP/1.1 在性能上的改进,以及这些改进背后的原理。

一、HTTP/1.1 的性能瓶颈

1.1 队头阻塞(Head-of-Line Blocking)

HTTP/1.1 要求同一连接上的请求必须按顺序处理:

1
2
3
连接 1:  |--请求A--|--请求B--|--请求C--|
^
等待 A 完成才能处理 B

如果请求 A 的响应很慢,后面的请求 B、C 都要等待,这就是队头阻塞

实际影响

  • 一个大文件阻塞整个连接
  • 慢 SQL 查询阻塞后续请求
  • 页面加载时间增加

1.2 并发限制

为了解决队头阻塞,浏览器会建立多个 TCP 连接:

1
2
3
连接 1:  |--请求A--|--请求D--|
连接 2: |--请求B--|--请求E--|
连接 3: |--请求C--|--请求F--|

但浏览器对同一域名的并发连接数有限制(通常 6-8 个):

浏览器 同域名最大连接数
Chrome 6
Firefox 6
Safari 6
Edge 6

问题

  • 现代网页平均 80+ 个资源请求
  • 只有 6 个并发连接远远不够
  • 每个连接都有 TCP 握手开销

1.3 头部冗余

HTTP/1.1 每次请求都要发送完整的头部:

1
2
3
4
5
6
7
8
GET /style.css HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...
Accept: text/css,*/*;q=0.1
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8
Accept-Encoding: gzip, deflate, br
Cookie: session=abc123; user_id=456; preferences=dark_mode...
Connection: keep-alive

问题

  • 典型请求头部 500-800 字节
  • 大部分头部是重复的(User-Agent、Cookie 等)
  • 100 个请求 = 50-80 KB 仅用于头部

1.4 文本协议效率低

HTTP/1.1 是文本协议,解析效率低:

1
2
3
4
需要逐字符扫描寻找:
- \r\n (行结束)
- : (键值分隔)
- 空格 (分隔符)

1.5 HTTP/1.1 优化手段(Workarounds)

为了绕过这些限制,开发者不得不使用各种技巧:

技巧 说明 缺点
域名分片 将资源分散到多个子域名 增加 DNS 查询和 TCP 连接
资源合并 将多个 JS/CSS 合并成一个 缓存效率降低
雪碧图 将多个图标合并成一张图 维护困难
内联资源 将小资源内联到 HTML 无法缓存

这些都是反模式,增加了开发复杂度。

二、HTTP/2 的性能改进

2.1 二进制分帧

HTTP/2 将消息分解为二进制帧传输:

1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 (文本):
GET /index.html HTTP/1.1\r\n
Host: example.com\r\n
\r\n

HTTP/2 (二进制帧):
+-------+-------+---------------+
| Length| Type | Flags |Stream | <- 9 字节固定头部
+-------+-------+---------------+
| Frame Payload |
+-------------------------------+

性能提升

  • 解析更快(固定位置读取 vs 字符扫描)
  • 更紧凑(无冗余空格和分隔符)
  • 便于多路复用

2.2 多路复用(Multiplexing)

HTTP/2 最重要的改进:单连接并行处理多个请求

1
2
3
4
5
6
HTTP/1.1 (串行):
连接1: |====请求A====|====请求B====|====请求C====|

HTTP/2 (多路复用):
连接1: |A帧|B帧|A帧|C帧|B帧|A帧|C帧|B帧|C帧|
└─请求A交错──┘ └─B交错─┘ └─C─┘

原理

  • 每个请求分配一个 Stream ID
  • 不同请求的帧可以交错发送
  • 接收方根据 Stream ID 重组
1
2
3
4
5
6
Stream 1 (请求A): [HEADERS] [DATA] [DATA]
Stream 3 (请求B): [HEADERS] [DATA]
Stream 5 (请求C): [HEADERS] [DATA] [DATA] [DATA]

实际传输顺序:
[H:1][H:3][D:1][H:5][D:3][D:1][D:5][D:5][D:5]

性能提升

  • 消除应用层队头阻塞
  • 减少 TCP 连接数(1 个连接足够)
  • 更好地利用带宽

实际对比

加载 100 个资源:

方式 TCP 连接数 总耗时
HTTP/1.1(无并发) 1 100 × RTT
HTTP/1.1(6 并发) 6 ~17 × RTT
HTTP/2(多路复用) 1 ~1-2 × RTT

2.3 头部压缩(HPACK)

HTTP/2 使用 HPACK 算法压缩头部:

静态表

61 个预定义的常用头部:

索引 头部名 头部值
1 :authority
2 :method GET
3 :method POST
4 :path /
5 :path /index.html

发送 GET / 只需 2 字节(索引 2 和 4)而不是完整文本。

动态表

连接期间累积的头部,可用索引引用:

1
2
3
4
5
第一次请求:
Cookie: session=abc123 <- 完整发送,加入动态表(索引 62)

第二次请求:
62 <- 只发送索引,1 字节

哈夫曼编码

对字符串值进行哈夫曼压缩:

1
2
原始: "www.example.com" (15 字节)
压缩后: 12 字节 (节省 20%)

压缩效果

场景 HTTP/1.1 头部大小 HTTP/2 头部大小 压缩率
首次请求 800 字节 200 字节 75%
后续请求 800 字节 20 字节 97%

100 个请求的头部总大小

  • HTTP/1.1: 80 KB
  • HTTP/2: ~3 KB

2.4 服务器推送(Server Push)

服务器可以主动推送资源,无需客户端请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
HTTP/1.1:
客户端: GET /index.html
服务器: 返回 index.html
客户端: (解析 HTML,发现需要 style.css)
客户端: GET /style.css <- 额外的往返
服务器: 返回 style.css

HTTP/2 Server Push:
客户端: GET /index.html
服务器: 返回 index.html
服务器: PUSH_PROMISE (style.css) <- 主动推送
服务器: 返回 style.css
无需额外往返!

流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
客户端                                服务器
| |
| HEADERS (GET /index.html) |
|------------------------------------>|
| |
| PUSH_PROMISE (style.css, stream 2) |
|<------------------------------------|
| HEADERS (200 OK, stream 1) |
|<------------------------------------|
| DATA (index.html, stream 1) |
|<------------------------------------|
| HEADERS (200 OK, stream 2) |
|<------------------------------------|
| DATA (style.css, stream 2) |
|<------------------------------------|
| |
客户端收到 HTML 时,CSS 已经在缓存中

注意:Server Push 在实践中使用较少,HTTP/3 已经弃用此功能。

2.5 流优先级(Stream Priority)

HTTP/2 允许客户端指定请求的优先级:

1
2
3
4
5
6
7
8
9
高优先级: HTML, CSS (渲染关键路径)
中优先级: JavaScript
低优先级: 图片, 字体

服务器按优先级分配带宽:
HTML: ████████████████████ (权重 256)
CSS: ████████████████ (权重 200)
JS: ████████ (权重 100)
图片: ████ (权重 50)

依赖树

1
2
3
4
5
6
7

/ \
HTML CSS
|
JS
/ \
img1 img2

JS 依赖 HTML,图片依赖 JS,确保关键资源优先加载。

2.6 流量控制(Flow Control)

HTTP/2 提供连接级和流级的流量控制:

1
2
3
4
5
6
7
+------------------+
| 连接级窗口 | <- 整个连接的流量限制
+------------------+
| | |
+----+ +----+ +----+
|流1 | |流2 | |流3 | <- 每个流独立的窗口
+----+ +----+ +----+

作用

  • 防止快速发送方压垮慢速接收方
  • 允许接收方控制内存使用
  • 避免单个流占用所有带宽

三、性能对比测试

3.1 页面加载时间

测试场景:加载包含 100 个资源的页面

网络条件 HTTP/1.1 HTTP/2 提升
高速(低延迟) 2.1s 1.2s 43%
4G(中等延迟) 4.5s 2.3s 49%
3G(高延迟) 12s 5.8s 52%

延迟越高,HTTP/2 优势越明显(因为减少了往返次数)。

3.2 连接数对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
HTTP/1.1 加载页面:
┌────────────────────────────────────────────┐
│ 域名: example.com │
│ 连接1 ───────────────── │
│ 连接2 ───────────────── │
│ 连接3 ───────────────── │
│ 连接4 ───────────────── │
│ 连接5 ───────────────── │
│ 连接6 ───────────────── │
│ 域名: cdn.example.com │
│ 连接7 ───────────────── │
│ 连接8 ───────────────── │
│ ... │
│ 总计: 12+ 个 TCP 连接 │
└────────────────────────────────────────────┘

HTTP/2 加载页面:
┌────────────────────────────────────────────┐
│ 域名: example.com │
│ 连接1 ═════════════════ (所有请求复用) │
│ 域名: cdn.example.com │
│ 连接2 ═════════════════ │
│ 总计: 2 个 TCP 连接 │
└────────────────────────────────────────────┘

3.3 带宽利用率

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 (6 连接):
连接1: ███░░░███░░░███░░░ (利用率 ~50%)
连接2: ░░███░░░███░░░███
连接3: ███░░░░░░███░░░░░░
...
大量空闲时间等待响应

HTTP/2 (1 连接):
连接1: █████████████████████ (利用率 ~95%)
持续传输,无等待

四、HTTP/2 的局限性

4.1 TCP 队头阻塞

HTTP/2 解决了应用层队头阻塞,但 TCP 层队头阻塞仍然存在:

1
2
3
4
5
6
7
TCP 层:
包1 [ACK] -> 包2 [丢失] -> 包3 [等待] -> 包4 [等待]
^
丢包导致后续包都被阻塞

HTTP/2 多路复用在此时受影响:
流1数据 [等待] 流2数据 [等待] 流3数据 [等待]

这是 HTTP/3 使用 QUIC(基于 UDP)的原因。

4.2 服务器推送的问题

  • 可能推送客户端已缓存的资源(浪费带宽)
  • 推送时机难以把握
  • 浏览器支持不一致

4.3 优先级实现差异

不同服务器对优先级的实现不同,效果参差不齐。

五、迁移建议

5.1 何时使用 HTTP/2

推荐使用 HTTP/2 的场景:

  • 资源数量多的页面
  • 高延迟网络环境
  • 移动端应用
  • API 服务(多个小请求)

5.2 迁移注意事项

之前的优化可能变成反优化

HTTP/1.1 优化 HTTP/2 建议
域名分片 移除,使用单域名
资源合并 拆分为小文件,利用缓存
雪碧图 使用单独图标,按需加载
内联资源 拆分为独立文件

5.3 服务器配置

Nginx 启用 HTTP/2:

1
2
3
4
5
6
7
server {
listen 443 ssl http2;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;

# HTTP/2 需要 HTTPS
}

六、总结

6.1 核心改进对比

特性 HTTP/1.1 HTTP/2
协议格式 文本 二进制
多路复用 不支持 支持
头部压缩 HPACK
服务器推送 支持
流优先级 支持
连接数 多个(6-8/域名) 单个
队头阻塞 严重 应用层无

6.2 性能提升来源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─────────────────────────────────────────────────────────┐
│ HTTP/2 性能提升 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 多路复用 │ │ 头部压缩 │ │ 二进制帧 │ │
│ ├─────────────┤ ├─────────────┤ ├─────────────┤ │
│ │ 消除队头阻塞 │ │ 减少 95% │ │ 更快解析 │ │
│ │ 减少连接数 │ │ 头部大小 │ │ 更紧凑 │ │
│ │ 降低延迟 │ │ 节省带宽 │ │ 便于复用 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ v v v │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 页面加载速度提升 40-60%,尤其高延迟网络 │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘

6.3 一句话总结

HTTP/2 通过多路复用消除队头阻塞,通过HPACK压缩头部,通过二进制分帧提升解析效率,使得单个 TCP 连接即可高效传输所有资源,大幅提升了 Web 性能。

参考资料