HTTP-1.1与HTTP-2对比#
一句话答案#
HTTP/2 改进:多路复用(一个连接并行多请求)、头部压缩(HPACK)、服务器推送、二进制帧。
核心要点
HTTP/1.1(1997):
+ 持久连接(Keep-Alive):复用 TCP 连接,减少建连开销
+ 管线化(Pipelining):可以连续发送请求,但服务端必须按顺序响应
问题:
队头阻塞(Head-of-Line Blocking):前一个请求卡住,后续请求全部等待
Header 重复:每次请求都携带完整 Header(Cookie 等),浪费带宽
并发靠多开 TCP 连接(浏览器通常 6~8 个并发连接/域名)plaintextHTTP/2(2015):
+ 多路复用(Multiplexing):同一 TCP 连接上并发多个请求/响应(Stream 机制)
→ 解决了 HTTP/1.1 的应用层队头阻塞
+ 头部压缩(HPACK):静态表+动态表+霍夫曼编码,大幅减少 Header 体积
+ 服务器推送(Server Push):服务端可以主动推送客户端需要的资源
+ 二进制分帧(Binary Framing):传输更高效(vs HTTP/1.1 的文本格式)
遗留问题:
TCP 层的队头阻塞仍然存在:
TCP 是字节流,一个 TCP 包丢失 → 整条连接上所有 Stream 都要等待重传plaintextHTTP/3(2022,基于 QUIC):
+ 底层协议从 TCP 换成 QUIC(基于 UDP 实现的可靠传输协议)
+ 彻底解决队头阻塞:QUIC 的 Stream 独立,一个 Stream 丢包不影响其他 Stream
+ 0-RTT / 1-RTT 连接建立:QUIC 将 TLS 握手和传输握手合并,减少往返次数
+ 连接迁移:基于 Connection ID 而非 IP+端口四元组,WiFi 切换 4G 后连接不中断
代价:UDP 穿越防火墙可能被封锁,部分网络环境下回退到 HTTP/2plaintext总结对比:
| 特性 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| 多路复用 | ❌ | ✅ | ✅ |
| 队头阻塞 | 严重 | 应用层解决,传输层仍有 | 完全解决 |
| 头部压缩 | ❌ | ✅ HPACK | ✅ QPACK |
| 底层协议 | TCP | TCP | QUIC(UDP) |
| TLS | 可选 | 实际强制 | 强制内置 |
HPACK 头部压缩(高频深挖)#
HTTP/1.1 每个请求都重复携带大量相同 Header(如 User-Agent、Cookie、Accept),一个请求头动辄上百字节甚至上 KB。HPACK 就是为了消除这种重复。
三个机制叠加:
| 机制 | 作用 |
|---|---|
| 静态表 | 协议预定义的 61 项常见 Header(如 :method: GET、:status: 200、content-type 等),固定不变。命中后只传一个索引号 |
| 动态表 | 连接级别维护,随请求增量追加。首次出现的 Header 存入动态表分配一个新索引;表满后按 FIFO 滑动淘汰最旧条目 |
| 霍夫曼编码 | 对没法走索引、必须传字面值的字符串(如具体的 Cookie 值)做变长编码,高频字符用短码,再压一层体积 |
压缩核心(一句话说清):
同一个 Header 第二次出现时,只传一个索引号(1~2 字节),而不是完整的
名:值字符串。
第一次请求:user-agent: Mozilla/5.0 ...(很长)
→ 静态表里有 user-agent 这个"名"的索引,但"值"是新的
→ 把完整键值对存入动态表,分配索引比如 62
第二次请求:user-agent 不变
→ 直接传索引 62,一个字节搞定 ✅plaintext- 索引 1~61:静态表(只读,所有连接共享同一份)
- 索引 62 起:动态表(每条连接独立,运行时增长)
这样一来,第二次及之后的请求里,绝大部分 Header 都退化成几个索引号,压缩率极高,这也是 HTTP/2 能在一条连接上高效并发的前提。
为什么 HTTP/3(QUIC)不能直接用 HPACK,要改成 QPACK?
HPACK 的动态表是连接级共享、随请求顺序增量更新的——编码端发”我把这个 Header 存进动态表索引 62”,解码端必须按相同顺序收到才能让两边的动态表保持一致。
- HTTP/2 跑在 TCP 上,TCP 保证字节流有序到达,所以动态表更新天然有序,没问题。
- HTTP/3 跑在 QUIC 上,各 Stream 独立、乱序到达。如果 Stream A 的请求引用了”动态表索引 62”,但定义索引 62 的那个包还堵在网络里没到,解码端就没法解析这个 Header,整个 Stream 被迫等待——这等于把队头阻塞从传输层又搬回了头部压缩层,恰好抵消了 QUIC 消除队头阻塞的优势。
QPACK 的解法:把头部数据拆成编码流(动态表更新指令,单独有序传输)和请求流(引用索引)两条逻辑通道,并允许请求显式声明它依赖的动态表版本;若依赖的更新还没到,可以选择阻塞等待或回退用字面值,从而把”动态表依赖”和”乱序到达”解耦,避免队头阻塞。
面试回答(2分钟版)
HTTP 协议经历了三个主要版本的演进。HTTP/1.1 引入了持久连接和管线化,但存在队头阻塞问题,前一个请求的响应没回来后续请求就得等,浏览器只能通过对同一域名开 6 到 8 个 TCP 连接来缓解。HTTP/2 做了几个关键改进:一是多路复用,在同一个 TCP 连接上通过 Stream 机制并发处理多个请求和响应,解决了应用层的队头阻塞;二是 HPACK 头部压缩,用静态表加动态表加霍夫曼编码大幅减少重复 Header 的传输量;三是二进制分帧,传输效率比 HTTP/1.1 的文本格式更高;四是服务器推送,可以主动推送客户端需要的资源。但 HTTP/2 的遗留问题是 TCP 层的队头阻塞仍然存在,一个 TCP 包丢失会导致整条连接上所有 Stream 都等待重传。HTTP/3 把底层协议从 TCP 换成基于 UDP 的 QUIC,每个 Stream 独立传输互不影响,彻底解决了队头阻塞,同时实现了 0-RTT 连接建立和基于 Connection ID 的连接迁移。
追问与易错
追问方向:
- “HTTP/2 的多路复用解决了什么?”→ 解决了 HTTP/1.1 的应用层队头阻塞,同一 TCP 连接上通过 Stream 机制并发处理多个请求响应,不再需要开多个 TCP 连接
- “HTTP/3 和 HTTP/2 区别?”→ HTTP/3 用 QUIC(基于 UDP)替代 TCP,彻底解决 TCP 层队头阻塞,支持 0-RTT 建连和连接迁移(WiFi 切 4G 不断连)
- “HTTP/2 的 Server Push 有什么用?”→ 服务端可在客户端请求 HTML 时主动推送关联的 CSS/JS 资源减少往返,但实际很少用,Chrome 已废弃该特性
- “HPACK 怎么压缩头部的?”→ 静态表(61 项预定义 Header)+ 动态表(连接级、随请求增量维护、FIFO 滑动淘汰)+ 霍夫曼编码;核心是同一 Header 第二次出现只传一个索引号而非完整字符串
- “为什么 HTTP/3 不用 HPACK 而用 QPACK?”→ HPACK 动态表依赖请求有序到达才能两端同步,QUIC 各 Stream 乱序到达会导致引用的动态表更新没到、Stream 被迫等待(头部压缩层的队头阻塞);QPACK 把动态表更新指令拆成单独有序的编码流并允许声明依赖版本,解耦”动态表依赖”与”乱序到达”
易错点:
- ❌ HTTP/2 完全解决了队头阻塞——TCP 层仍有队头阻塞
- ❌ HTTP/2 必须 HTTPS——规范不要求但浏览器要求
- ❌ HPACK 动态表是全局/跨连接共享的——动态表是连接级别的,每条连接各自维护一份;只有静态表是所有连接共享的只读表