HTTP/2 基本概念学习笔记
Category(分类): NET Protocol Status: 未知
本文保留原文关于 HTTP/2 二进制分帧、多路复用、HPACK、服务器推送、优先级、流重置和流量控制的学习路径,并结合 RFC 9113、RFC 9218、RFC 9114 和 QUIC 的现代说明修正过时内容。
HTTP/2 规范最初于 2015 年以 RFC 7540 发布。当前正式规范是 RFC 9113(2022),它取代了 RFC 7540。现代浏览器和服务器普遍支持 HTTP/2,但实际能否使用还取决于 TLS、ALPN、代理、CDN 和服务器配置。

原文图片记录于 2018 年,具有历史参考价值,不能代表当前所有浏览器和服务器的支持比例。
作为对 HTTP/1.x 传输效率的改进,HTTP/2 在不改变 HTTP 方法、状态码、URI 和大部分首部语义的前提下,引入了二进制分帧、多路复用、首部压缩和流量控制等机制。
1. 过去和现在
HTTP/1.1 最初由 RFC 2068 于 1997 年发布,随后 RFC 2616 于 1999 年发布。后来 HTTP/1.1 的语义和消息语法被 RFC 9110、RFC 9111、RFC 9112 等新规范重新整理。HTTP/1.x 被广泛使用了很长时间,但随着网页资源数量和网络交互复杂度增加,一些传输层面的限制逐渐明显。

HTTP/1.x 常见的性能问题包括:
- 连接并发受客户端实现限制:早期浏览器常限制同一主机的并发连接数为 2,现代浏览器对 HTTP/1.1 通常使用更高的连接数;这属于浏览器策略,不是 HTTP 标准规定的固定值;
- HTTP/1.1 请求级队头阻塞:同一个连接上的管线化请求和响应受到顺序限制,后面的响应可能等待前面的响应;浏览器通常通过多个 TCP 连接缓解这个问题;
- 没有统一的首部压缩机制:HTTP/1.x 首部通常以文本表示,重复的 Cookie、User-Agent 等首部会造成额外开销。应用或代理可以使用其他压缩方式,但 HTTP/1.x 本身没有 HPACK 这样的通用机制;
- 缺少统一的请求优先级信号:服务器可以自行调度,但 HTTP/1.x 没有像现代 HTTP Priority 那样的统一优先级机制;
- 请求—响应模型不适合任意时刻的服务端消息:HTTP 传统上由客户端发起请求、服务端返回响应,但也可以通过长轮询、SSE、流式响应或 WebSocket 实现不同形式的推送。
HTTP/2 使用以下技术改善传输效率:
- 二进制分帧;
- 多路复用;
- HPACK 首部压缩;
- 流级和连接级流量控制;
- Stream 重置;
- 可扩展的优先级机制。
需要特别注意:HTTP/2 解决了 HTTP/1.x 中一部分请求级队头阻塞,但多个 Stream 仍然共享一条 TCP 连接,因此 TCP 丢包仍可能阻塞整条 HTTP/2 连接。HTTP/3 使用 QUIC 的独立 Stream,进一步减少跨 Stream 的传输层队头阻塞。
2. HTTP/2
HTTP/2 的前身是 SPDY 协议。SPDY 是 Google 主导的历史实验性应用层协议,HTTP/2 的早期草案大量借鉴了 SPDY 的设计。
HTTP/2 的目标是在维持 HTTP 语义的前提下改善传输性能:
- HTTP 方法、状态码、URI 和大部分首部字段的语义继续保留;
- HTTP/2 改变的是连接上的编码、分帧和传输方式;
- 应用看到的仍然是 HTTP 请求和响应,而不是 SPDY 专有语义。
HTTP/2 的主要特性包括:
- 二进制帧层;
- 使用帧作为传输单位;
- 多路复用;
- HPACK 首部压缩;
- 可选的服务器推送;
- 流量控制;
- Stream 重置;
- 历史上的 Stream 优先级与依赖关系;
- 连接级和 Stream 级错误处理。
HTTP/2 是否强制使用 HTTPS?
HTTP/2 规范本身没有要求必须使用 TLS。HTTP/2 存在明文 h2c 模式,但浏览器通常只支持通过 TLS ALPN 协商的 HTTP/2,因此面向浏览器的 HTTP/2 一般表现为 HTTPS。
HTTP/2 使用 TCP;不要把 HTTP/2 和 HTTP/3 混为一谈:
HTTP/1.1 → TCP
HTTP/2 → TCP
HTTP/3 → QUIC/UDP
2.1 二进制表示
在 HTTP/1.x 中,请求行和首部通常使用可读的文本格式。HTTP/2 在连接层使用二进制帧,帧头中的长度、类型、标志和 Stream 标识符都有明确的字段位置,解析时不需要依赖空格、换行等文本分隔符。
二进制分帧的主要价值是:
- 便于可靠解析;
- 支持不同 Stream 的帧交错传输;
- 便于实现帧级控制;
- 支持连接级和 Stream 级错误处理。
“二进制一定更小”并不准确。实际传输体积还取决于 HPACK、内容压缩、帧头开销和应用数据。HTTP 响应正文可以是文本、图片、视频或其他任意字节,并不因为 HTTP/2 就变成某一种二进制业务格式。
2.2 二进制分帧
HTTP/2 在 HTTP 语义和底层传输之间增加了二进制分帧层。HTTP/2 规范定义的帧被封装到 TCP 字节流中;每个帧都有固定的 9 字节帧头和可变长度的帧载荷。

基本帧结构如下:
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================+
| Frame Payload (0...) ...
+-----------------------------------------------+
Length:24 位,表示帧载荷长度,不包括 9 字节帧头;Type:8 位,表示帧类型;Flags:8 位,具体含义由帧类型解释;R:保留位,发送时必须为 0,接收时通常忽略;Stream Identifier:31 位 Stream 标识符;- 帧的最大载荷长度受
SETTINGS_MAX_FRAME_SIZE限制,默认值通常为 16,384 字节,允许的最大值为 16,777,215 字节。
RFC 7540 基础规范定义了 10 种基础帧类型:
DATA:传输请求或响应内容;HEADERS:传输首部;PRIORITY:旧版优先级信号,当前语义已被弃用;RST_STREAM:终止一个 Stream;SETTINGS:协商连接参数;PUSH_PROMISE:声明服务端推送;PING:测量连接往返时间或确认连接可用;GOAWAY:优雅关闭连接;WINDOW_UPDATE:更新流量控制窗口;CONTINUATION:继续传输首部块。
HTTP/2 还允许扩展帧类型,因此“规范一共只有 10 种帧”应理解为基础规范的 10 种帧,而不是所有实现永远只能使用这 10 种。
一个 HTTP/2 请求在逻辑上仍然包含方法、路径、首部和内容,只是它们被编码到 HEADERS、CONTINUATION 和 DATA 等帧中:

2.3 多路复用(Multiplexing)和 Stream
RFC 9113 将 Stream 描述为:在一个 HTTP/2 连接中,由客户端和服务器交换的独立帧序列。Stream 是逻辑概念,不是额外的一条 TCP 连接。

一个 HTTP/2 连接可以包含多个并发 Stream。不同 Stream 的帧可以在同一条 TCP 连接中交错传输;同一个 Stream 内的帧则必须按照协议规定的顺序处理。
可以把三者理解为:
TCP 连接
└── HTTP/2 连接
├── Stream 1
│ ├── HEADERS
│ └── DATA
├── Stream 3
│ ├── HEADERS
│ └── DATA
└── Stream 5
├── HEADERS
└── DATA
多路复用示例
假设 TCP 连接已经建立,客户端需要同时发起两个请求。HTTP/1.1 可能需要多个连接,HTTP/2 则可以为它们创建两个 Stream:

实际传输时,帧仍然会沿着一条 TCP 字节流依次发送,但来自不同 Stream 的帧可以交错:
HEADERS(stream 1)
HEADERS(stream 3)
DATA(stream 3)
DATA(stream 1)
DATA(stream 3)
这里的“并发”是多个 Stream 的进展可以交错进行,并不意味着多个帧在同一条 TCP 字节流上物理同时到达。
需要注意:
- 不同 Stream 的帧可以交错传输;
- 同一个 Stream 内部的帧顺序必须保持;
- 初始请求或响应首部通常先于其 DATA,但尾部首部可以在 DATA 之后出现;
CONTINUATION必须按照首部块规则紧跟相关首部帧;- 不同 Stream 的完成顺序可以不同,服务端可以根据调度策略先发送更重要的 Stream。
多路复用减少了 HTTP/1.1 为了并发而创建大量 TCP 连接的需求,也减少了 HTTP/1.1 请求级队头阻塞。但它没有消除 TCP 层队头阻塞:一旦 TCP 某个数据包丢失,后续字节需要等待重传,多个 HTTP/2 Stream 可能一起受影响。
HTTP/3 将 HTTP 映射到 QUIC 的独立 Stream,某个 Stream 的丢包通常不会阻塞其他 Stream,但单个 Stream 内部仍然是有序可靠传输。
HTTP/1.1 与 HTTP/2 的性能示意
下面的图片是原文历史性能示意,具体收益会受到网络、缓存、资源数量、服务端实现和浏览器版本影响:


过去的 HTTP 性能优化经常关注带宽和延迟。对包含大量小资源的网页来说,连接建立、往返延迟、请求调度和队头阻塞都可能影响首屏时间。

TCP 慢启动会影响新建连接在短时间内能够发送的数据量。HTTP/2 让多个 Stream 共享一条 TCP 连接,通常可以减少重复握手和慢启动开销,但也带来共享 TCP 拥塞状态和 TCP HOL 的影响。因此不能简单断言 HTTP/2 在所有网络条件下都比 HTTP/1.1 快,更不能把历史 Demo 的结果当成固定结论。
对于 HTTP/2,通常不再需要为了并发而进行 HTTP/1.1 时代的域名分片、资源拼接和大量 Sprite;但是否合并资源、是否预加载,仍应根据缓存粒度、构建工具、网络和实际性能数据决定。
2.4 首部压缩:HPACK
HTTP 的语义是无状态的,单个请求应携带服务端理解该请求所需的首部信息。但这并不意味着所有业务状态都要放在每个请求中,也不意味着身份信息只能放在 Cookie 中。Cookie、Session、Token 等是应用层状态管理方案,安全性主要依赖 TLS、认证和权限设计。
HTTP/1.x 没有统一的首部压缩机制。大量请求经常重复携带 Cookie、User-Agent、Accept 等首部,会造成额外开销。HTTP/2 使用 HPACK(RFC 7541)压缩首部字段:

HPACK 使用静态表、动态表和索引表示法:
- 静态表预先定义常见首部字段和值;
- 动态表在连接两端维护近期出现的首部字段;
- 后续首部块可以使用索引引用已有条目;
- 无法或不适合索引的字段可以直接传输;
- 敏感字段可以使用不加入动态表的表示方式。
首部表不是“每个首部只发送一次”。条目可能被淘汰、连接可能重新建立、服务端可能选择不索引某些字段,代理和客户端也可能拥有不同的压缩状态。

HPACK 的动态表状态通常与一条 HTTP/2 连接绑定,不应跨连接假定双方仍然拥有相同的表。HPACK 只压缩首部,不压缩响应正文;正文压缩仍可使用 gzip、Brotli 等内容编码。
2.5 服务器推送
HTTP/2 Server Push 是一种可选能力:当客户端请求资源 X 时,服务端可以通过 PUSH_PROMISE 预先声明并发送客户端可能需要的资源 Z,而不必等待客户端明确请求 Z。
原文将其称为“缓存推送”,这个称呼便于理解,但不是正式的协议名称。客户端可以通过 HTTP/2 设置禁用服务器推送,也可以在不需要时使用 RST_STREAM 取消对应的推送 Stream。
传统流程是:

网页先请求 Document,浏览器解析 Document 后,再发现 CSS、JavaScript、图片等资源并发起后续请求。过去常见的优化方式包括内联关键 CSS、合并资源、CSS Sprite、压缩 JavaScript 等:

HTTP/2 Server Push 的设想是:服务端在返回 Document 时,主动推送它预测客户端会需要的资源:

但需要注意以下限制:
- 推送是可选能力,客户端可以禁用或取消;
- 推送资源是否进入缓存,取决于缓存策略、响应首部和客户端实现,并非必然;
- 服务端预测错误时,会浪费带宽并挤占真正重要资源;
- 客户端已经缓存资源时,重复推送可能反而降低性能;
- Server Push 是连接级/协议级行为,不等同于 CDN 缓存,也不能替代 CDN;
- 主流浏览器已经禁用或移除了 HTTP/2 Server Push 的实际支持,Chrome/Chromium 等浏览器不再将它作为常规网页优化方案。
原文提到的 cache-digest 是历史草案,并没有成为当前通用标准。现代网页通常优先考虑:
Link: <...>; rel=preload;103 Early Hints;- 合理的 Cache-Control 和 CDN 缓存;
- 更准确的资源依赖分析;
- 根据真实性能数据决定是否预加载。
HTTP/2 仍然可以在非浏览器或特定基础设施中使用 Server Push,但新项目不应默认依赖浏览器支持。
服务器推送也不能减少或替代 CDN。CDN 可以通过地理位置、边缘缓存和接近用户的节点减少网络往返,而 HTTP/2 主要改善连接内的传输组织方式,两者解决的问题不同。
2.6 优先级与依赖关系
HTTP/2 早期规范使用 Stream 依赖树和权重描述优先级:某个 Stream 可以依赖另一个 Stream,服务端根据权重进行调度。客户端还可以通过 PRIORITY 帧动态调整这些信息。
这部分内容需要加上版本说明:RFC 9113 已弃用 RFC 7540 的依赖树、权重和旧优先级语义,保留相关线格式主要是为了兼容。现代 HTTP 优先级应参考 RFC 9218:
- 使用
Priority首部表达可扩展优先级; - 在 HTTP/2 中可以使用
PRIORITY_UPDATE扩展帧; - 具体调度仍由服务器实现决定,客户端发送优先级不代表服务器必须按该顺序发送。
优先级可以帮助服务端在资源有限时调度重要资源,但不能保证页面一定按某个固定顺序加载,也不能替代缓存和资源依赖设计。
2.7 Stream 可重置
HTTP/1.x 中,如果一个请求或响应正在传输,取消它通常需要关闭连接,或者依赖客户端关闭写方向等实现行为。关闭整个 TCP 连接会影响该连接上的其他请求。
HTTP/2 可以通过 RST_STREAM 终止单个 Stream,而不必关闭整个 TCP 连接:
- 取消不再需要的响应;
- 终止发生错误的请求;
- 释放该 Stream 的传输资源。
但 RST_STREAM 不是事务回滚。服务端可能已经收到请求内容或执行了业务逻辑,尤其是 POST、支付和订单操作,不能因为客户端重置 Stream 就认为业务一定没有发生。
2.8 流量控制
HTTP/2 同时具有两级流量控制:
- Stream 级窗口:限制某个 Stream 上可发送的 DATA 数据量;
- Connection 级窗口:限制整个 HTTP/2 连接上所有 Stream 的 DATA 总量。
双方通过 WINDOW_UPDATE 增大对应窗口。只有 DATA 帧受到流量控制,HEADERS、RST_STREAM、SETTINGS 等控制帧不受 DATA 窗口限制。
接收端需要根据自身缓冲能力更新窗口;发送端在窗口耗尽时必须暂停发送对应的数据。流量控制用于防止快速发送方压垮接收方,但它与 TCP 拥塞控制是两套不同的机制:
- TCP 拥塞控制关注网络路径能承受多少数据;
- HTTP/2 流量控制关注接收端和 Stream 能处理多少数据。
3. HTTP/2 与 HTTP/3 的关系
HTTP/2 和 HTTP/3 都保留了 HTTP 的方法、状态码和大部分语义,但底层传输方式不同:
| 项目 | HTTP/2 | HTTP/3 |
|---|---|---|
| 底层传输 | TCP + TLS(常见) | QUIC + UDP,QUIC 内置 TLS 1.3 握手 |
| 多路复用 | 多个 Stream 共享一条 TCP 字节流 | 多个独立 QUIC Stream |
| TCP 层 HOL | 仍然存在 | 不使用 TCP,减少跨 Stream HOL |
| 首部压缩 | HPACK | QPACK |
| HTTP/2 Server Push | 规范支持但浏览器支持很少 | 协议仍可表达,但浏览器支持和实际使用同样有限 |
| 连接迁移 | 依赖 TCP 连接 | QUIC 支持连接迁移,具体受网络和实现限制 |
HTTP/3 并不是 HTTP/2 的简单换端口版本,而是把 HTTP 映射到 QUIC 后重新定义了部分连接、Stream 和首部压缩行为。
4. 总结
HTTP/2 能带来的主要好处包括:
- 使用二进制分帧,便于解析和控制;
- 通过多个 Stream 复用一个连接,减少 HTTP/1.1 为并发而建立的大量连接;
- 通过 HPACK 减少重复首部传输,但不保证所有请求都获得相同压缩收益;
- 通过 Stream 重置取消单个传输,不必关闭整个连接;
- 通过 Stream 级和 Connection 级流量控制保护接收端;
- 可以使用优先级机制辅助服务器调度资源;
- 可选的 Server Push 在协议上仍存在,但已不适合作为现代浏览器网页的默认优化方案;
- HTTP/2 仍然受到 TCP 层队头阻塞影响,HTTP/3/QUIC 在这方面进一步改进。
HTTP/2 与 CDN 并不互相替代。CDN 主要减少地理距离和缓存访问延迟,HTTP/2 主要改善连接内的帧组织和资源传输,实际系统通常需要结合 CDN、缓存、TLS、连接复用和性能监控共同优化。