面试知识库
进阶

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 个并发连接/域名)
plaintext

HTTP/2(2015):

+ 多路复用(Multiplexing):同一 TCP 连接上并发多个请求/响应(Stream 机制)
  → 解决了 HTTP/1.1 的应用层队头阻塞
+ 头部压缩(HPACK):静态表+动态表+霍夫曼编码,大幅减少 Header 体积
+ 服务器推送(Server Push):服务端可以主动推送客户端需要的资源
+ 二进制分帧(Binary Framing):传输更高效(vs HTTP/1.1 的文本格式)

遗留问题:
  TCP 层的队头阻塞仍然存在:
  TCP 是字节流,一个 TCP 包丢失 → 整条连接上所有 Stream 都要等待重传
plaintext

HTTP/3(2022,基于 QUIC):

+ 底层协议从 TCP 换成 QUIC(基于 UDP 实现的可靠传输协议)
+ 彻底解决队头阻塞:QUIC 的 Stream 独立,一个 Stream 丢包不影响其他 Stream
+ 0-RTT / 1-RTT 连接建立:QUIC 将 TLS 握手和传输握手合并,减少往返次数
+ 连接迁移:基于 Connection ID 而非 IP+端口四元组,WiFi 切换 4G 后连接不中断

代价:UDP 穿越防火墙可能被封锁,部分网络环境下回退到 HTTP/2
plaintext

总结对比:

特性HTTP/1.1HTTP/2HTTP/3
多路复用
队头阻塞严重应用层解决,传输层仍有完全解决
头部压缩✅ HPACK✅ QPACK
底层协议TCPTCPQUIC(UDP)
TLS可选实际强制强制内置

HPACK 头部压缩(高频深挖)#

HTTP/1.1 每个请求都重复携带大量相同 Header(如 User-AgentCookieAccept),一个请求头动辄上百字节甚至上 KB。HPACK 就是为了消除这种重复。

三个机制叠加:

机制作用
静态表协议预定义的 61 项常见 Header(如 :method: GET:status: 200content-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 动态表是全局/跨连接共享的——动态表是连接级别的,每条连接各自维护一份;只有静态表是所有连接共享的只读表