抛弃TCP改用UDP,HTTP3疯了吗?

HTTP协议用过这么多年,但HTTP/1.1、HTTP/2、HTTP/3到底有什么区别?各自解决了什么问题,又各自留下了什么遗憾?这篇把三个版本放在同一张桌子上,逐项对比。


原文地址

墨渊书肆/抛弃TCP改用UDP,HTTP3疯了吗?


先说结论

维度 HTTP/1.1 HTTP/2 HTTP/3 (QUIC)
传输层协议 TCP TCP UDP
多路复用 ❌ 不支持 ✅ 支持(流级别) ✅ 支持(流级别)
头部压缩 ❌ 纯文本重复发送 ✅ HPACK(静态字典+动态字典+Huffman) ✅ QPACK(改进版HPACK)
服务端推送 ❌ 不支持 ✅ 支持 ❌ 已移除(几乎没人用)
队头阻塞 TCP层级阻塞 + 请求级阻塞 TCP层级仍阻塞 彻底解决
连接建立 1-RTT(TCP握手) 1-RTT(TCP握手) + TLS 0-RTT(恢复连接时)
连接迁移 ❌ IP变了就断 ❌ IP变了就断 ✅ 网络切换不断连

一句话总结:HTTP/2解决了应用层的队头阻塞,HTTP/3解决了传输层的队头阻塞


HTTP/1.1到底慢在哪?

问题一:连接级队头阻塞(Connection-level Head-of-Line Blocking)

HTTP/1.1的队头阻塞发生在TCP连接层面 ------浏览器对同一域名有连接数限制,Chrome限制6个TCP连接。这意味着同时最多只能发6个请求,第7个必须等前面某个完成才能发。

md 复制代码
浏览器想加载一个页面,需要请求10个资源:
  CSS、JS、图片1、图片2、图片3、字体、API数据...

但只有6个通道:
  [通道1: CSS ████████████ 完成]
  [通道2: JS  ███████████████████████ 还在传...]
  [通道3: 图片1 ██████ 完成]
  [通道4: 图片2 ████████████████ 还在传...]
  [通道5: 字体 ██████████████████████████████████ 还在传...]
  [通道6: API  ████████████ 完成]

图片3、图片4... 全在排队等!
因为通道2的JS文件特别大,堵住了后面的所有请求。

这就是连接级队头阻塞:一个慢请求占满通道,导致后面的所有请求排队。

问题二:头部冗余

HTTP头部是纯文本,每次请求都完整携带:

http 复制代码
GET /api/user HTTP/1.1
Host: api.example.com
Accept: application/json
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
User-Agent: Mozilla/5.0 ...
Accept-Language: zh-CN,zh;q=0.9
Cookie: session=abc123; theme=dark; ...

--- 第二次请求,同样的头部再发一遍 ---

GET /api/order HTTP/1.1
Host: api.example.com       ← 一样
Accept: application/json    ← 一样
Content-Type: application/json ← 一样
Authorization: Bearer eyJhbGciOiJIUzI1NiIs... ← 一样
User-Agent: Mozilla/5.0 ... ← 一样
Accept-Language: zh-CN,zh;q=0.9 ← 一样
Cookie: session=abc123; ... ← 差不多

一次页面加载可能发出几十个请求,每个请求重复携带500-2000字节的头部。大部分字段完全相同,纯浪费带宽。

问题三:无法主动推送

用户请求了HTML,服务器知道HTML里引用了CSS和JS。但按照HTTP/1.1的规则,服务器必须等浏览器解析完HTML、发现需要CSS和JS、再发起请求------白白多等了一个往返时间(RTT)。


HTTP/2怎么解决这些问题?

核心变化:二进制分帧层(Binary Framing)

这是HTTP/2最根本的改变。HTTP/1.1是基于文本的,HTTP/2把所有数据拆成更小的单元------帧(Frame)

md 复制代码
HTTP/1.1:一条完整的请求/响应 = 一大块文本

HTTP/2:一条消息 → 拆成多个帧
         ┌─────────────────────────────┐
         │  帧类型  │  帧标识  │   负载   │
         ├─────────┼─────────┼─────────┤
         │ HEADERS │ Stream3 │ 请求头   │
         │ DATA    │ Stream3 │ 请求体   │
         │ HEADERS │ Stream5 │ 请求头   │
         │ DATA    │ Stream5 │ 请求体   │
         │ HEADERS │ Stream7 │ 请求头   │
         │ DATA    │ Stream7 │ 请求体   │
         └─────────┴─────────┴─────────┘
                    ↓
           在同一个TCP连接上交错发送

帧的类型:

帧类型 作用
HEADERS 携带HTTP头部信息
DATA 携带HTTP body
SETTINGS 协商连接参数
PING 心跳检测
WINDOW_UPDATE 流量控制
RST_STREAM 终止某个流(不影响其他)
PUSH_PROMISE 服务端推送承诺

多路复用:一个连接搞定一切

有了二进制分帧,HTTP/2可以在同一个TCP连接上同时发送多个请求/响应,互不干扰:

yaml 复制代码
HTTP/1.1(6个连接):
  TCP连接1 ──→ 请求A ──→ 响应A
  TCP连接2 ──→ 请求B ──→ 响应B
  TCP连接3 ──→ 请求C ──→ 响应C
  ...

HTTP/2(1个连接):
  TCP连接1 ─┬─→ Stream1: 请求A ←→ 响应A(交错传输)
            ├─→ Stream3: 请求B ←→ 响应B(交错传输)
            ├─→ Stream5: 请求C ←→ 响应C(交错传输)
            └─→ Stream7: 请求D ←→ 响应D(交错传输)

好处很明显:

  • 浏览器不再受6连接数限制
  • 一个慢请求不会阻塞其他请求
  • 减少TCP连接建立的开销(每次握手都要1-RTT)

HPACK头部压缩

HTTP/2用HPACK算法压缩头部,三层机制配合:

第一层:静态字典

常用头部组合预定义好,编号0-61,直接用编号代替:

md 复制代码
编号2 : method: GET
编号4 : path: /
编号6 : scheme: http
编号20: content-type: application/json
...

第二层:动态字典

连接期间出现过的头部存下来,后续直接用编号引用。假设第1个请求完整发送了:

md 复制代码
:method: GET
:path: /api/user
:scheme: https
authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

这些被加入动态字典,编号依次为 61、62、63、64...

第2个请求要发完全相同的 authorization,只需要发一个字节 0x3E(二进制 00111110,对应编号62)------连"authorization"这个词都不用发,解码器查字典就知道是哪个头部。实际传输量从几十字节压缩到1-2字节。

第三层:Huffman编码

对还没被字典收录的字段,用Huffman算法进一步压缩。HTTP头部中大量ASCII字符,Huffman编码后体积能减少20-30%。

实际效果: 头部大小从平均800字节降到约200字节,压缩率约75%。

服务端推送(Server Push)

服务器可以在客户端请求之前主动推送资源:

md 复制代码
浏览器请求:GET /index.html

服务器返回HTML的同时,顺便推:
  PUSH_PROMISE: /style.css
  PUSH_PROMISE: /main.js

浏览器收到HTML时,CSS和JS已经在路上了
省了一次RTT

但这个功能在实际中几乎没人用。 原因不少:

  • 推送的资源浏览器可能已经有了(缓存命中),白推了
  • 推送占用带宽,可能挤掉真正需要的资源
  • 很难精确判断该推什么、什么时候推
  • 开发调试困难

所以HTTP/3直接把这个特性移除了。Google的数据显示,Server Push的实际使用率不到0.1%。


HTTP/2还有问题吗?

有。而且是个致命问题。

TCP级队头阻塞(Transport-layer Head-of-Line Blocking)

HTTP/2用多路复用解决了HTTP层面的队头阻塞------多个请求可以交错发送互不干扰。但它跑在TCP之上,TCP有自己的问题:

md 复制代码
TCP保证有序交付:包1、包2、包3 必须按顺序到达

如果包2丢了:
  包1 到达 ✓ → 交给应用层
  包2 丢失 ✗ → 等待重传...
  包3 到达 ✗ → 不能交给应用层!必须等包2先到!

结果:包3明明已经到了,却因为包2丢了而被阻塞
这就是 **TCP级队头阻塞**:传输层丢一个包,应用层所有流都被卡住。

在网络状况不好的场景下(移动网络、跨运营商),丢包率升高,TCP重传频繁,整个连接的所有流都会受影响------哪怕只有其中一个流的包丢了。

具体影响有多大? Google测试数据显示:在1%丢包率的网络环境下,HTTP/2的吞吐量下降比HTTP/1.1还严重。因为HTTP/1.1可以开多个TCP连接,一个连接丢包不影响其他;而HTTP/2只有一个连接,一损俱损。


HTTP/3(QUIC):彻底换底座

既然问题出在TCP上,那就不走TCP了。HTTP/3底层换成QUIC协议 ,而QUIC跑在UDP之上。

为什么选UDP而不是继续改TCP?

TCP的问题不是小修小补能解决的------它的有序交付机制是协议层面写死的,改不了。要彻底解决队头阻塞,只能换协议。

但重新设计一个传输层协议推广难度极大(操作系统内核、中间设备全要支持)。所以QUIC选择在用户空间实现,跑在UDP之上------UDP足够简单,几乎所有网络设备都能放行。

QUIC的核心改进

1. 无队头阻塞

QUIC的每个Stream独立有序,但Stream之间无序:

yaml 复制代码
TCP(HTTP/2):
  Connection ──→ [Stream1][Stream2][Stream3]  包2丢了 → 全阻塞

QUIC(HTTP/3):
  Connection ──┬→ Stream1: [包1][包2][包3]  独立有序
              ├→ Stream2: [包1][包2][包3]  独立有序,互不影响
              └→ Stream3: [包1][包2][包3]  Stream2丢包只阻塞Stream2

Stream2的包丢了,只影响Stream2本身。Stream1和Stream3照常交付。

2. 0-RTT连接建立

传统TLS握手需要2-RTT(ECDHE密钥交换场景):

yaml 复制代码
首次连接(HTTP/1.1 或 HTTP/2):
  Client          Server
    │               │
    │──ClientHello──>│          RTT 1: 发起握手
    │<─ServerHello───│          RTT 2: 服务器响应 + 证书
    │──KeyExchange──>│          RTT 3: 密钥交换
    │<─Finished──────│          RTT 4: 握手完成
    │               │
    │════ 数据传输 (已加密) ════│    终于可以发数据了
    │               │

首次连接(QUIC/HTTP/3):
  Client          Server
    │               │
    │──ClientHello──>│          RTT 1: 握手 + 第一个请求一起发
    │<─ServerHello───│          RTT 2: 响应 + 确认
    │               │
    │════ 数据传输 (已加密) ════│    少了一个RTT

恢复连接(QUIC/HTTP/3):
  Client          Server
    │               │
    │──请求数据 + 恢复凭证 ─>│   RTT 0: 直接带数据
    │<──────── 数据 ─────────│    第一次往返就能拿到响应

对于移动端用户来说,0-RTT 意味着打开 App 的首屏时间显著缩短。

代价也要说清楚: 0-RTT 存在"重放攻击"风险------攻击者截获历史请求和密钥材料,重新发送相同请求到服务器。解决方案是请求加上"anti-replay"机制,服务端对相同请求只执行一次。所以0-RTT适合读请求,不适合写请求(下单、转账等)。

3. 连接迁移

换个WiFi或者从4G切到5G,IP地址会变。TCP用四元组(源IP、源端口、目的IP、目的端口)标识连接,IP一变连接就断了,得重新握手。

QUIC用Connection ID标识连接,跟IP无关:

yaml 复制代码
手机从WiFi切到4G:
  TCP:IP变了 → 连接断开 → 重新三次握手 → 重新TLS握手 → 恢复数据传输(卡顿明显)

  QUIC:IP变了 → Connection ID没变 → 连接保持 → 继续传输(用户无感知)

这对移动端体验提升很大------地铁里进出站、电梯上下,网络频繁切换,QUIC不断连。

4. 内置TLS 1.3

QUIC强制使用TLS 1.3加密,不像TCP+TLS是两层拼起来的。加密是协议原生的一部分,不存在"明文握手阶段"这种漏洞窗口。

QPACK:改进的头部压缩

HTTP/3用 QPACK 替代 HPACK,解决的是HPACK在多路复用场景下的队头阻塞问题。

HPACK为什么会阻塞?

HPACK的动态字典是"按顺序积累"的------第1个请求的头部加入字典,第2个请求复用第1个的字典状态。如果第1个请求的HEADERS帧丢了,第2个请求的帧就解不出来了(字典状态对不上)。

这跟TCP的队头阻塞是一个道理,只是堵的是头部解码器,不是传输层。

QPACK怎么解决的?

QPACK引入单向流(Uni-directional Stream)机制------字典同步和数据传输彻底分开:

md 复制代码
QPACK 两条独立流:

  编码器 ──单向流──> 解码器
  (只发送字典变化,不携带数据)

  编码器 ──单向流──> 解码器
  (携带数据帧,但引用字典编号,不包含完整头部)

字典同步走专门的流,跟数据流完全隔离。即使数据流丢包,字典同步流不受影响,解码器仍然可以处理其他帧。

效果:即使某个流的HEADERS帧丢失,不会影响其他流的头部解码,真正实现了"一Stream堵了,其他Stream照常工作"。


三代HTTP对比全景图

应用层:

特性 HTTP/1.1 HTTP/2 HTTP/3
协议格式 文本协议 二进制分帧 二进制分帧
头部压缩 无,每次重复发送 HPACK(静态+动态字典+Huffman) QPACK(单向流隔离)
多路复用 无(连接数限制6个) 支持(流级别) 支持(流级别)
服务端推送 不支持 支持 已废弃

安全层:

特性 HTTP/1.1 HTTP/2 HTTP/3
加密 TLS可选 TLS必须 TLS 1.3内置(原生)
握手 独立层 独立层 集成在QUIC内部

传输层:

特性 HTTP/1.1 HTTP/2 HTTP/3
传输协议 TCP TCP UDP
可靠性 TCP保证有序可靠 TCP保证有序可靠 QUIC在用户空间实现可靠+有序
队头阻塞 TCP级阻塞(丢包堵所有连接) TCP级阻塞(丢包堵所有流) 无队头阻塞(Stream独立)

实际性能差多少?

理论 vs 现实

理论上HTTP/3应该全面碾压HTTP/2,但实际效果取决于场景:

场景 HTTP/2表现 HTTP/3表现 说明
理想网络(低延迟低丢包) 已经很快 略快 差距不大,瓶颈不在协议
高延迟网络(跨国) 较慢 明显更快 0-RTT和减少握手轮次优势大
高丢包网络(移动弱网) 性能下降严重 表现稳定 无队头阻塞的优势体现
大文件下载 差不多 单流场景差异不大
小文件密集请求 一般 更好 多路复用+头部压缩叠加

什么时候该升级?

  • 网站主要面向国内用户:HTTP/2已经够用,国内网络质量好,HTTP/3提升有限
  • 有海外用户或移动端为主:HTTP/3值得上,弱网和跨境场景收益明显
  • 实时性要求高(直播、在线协作):HTTP/3的低延迟优势重要
  • 还在用HTTP/1.1:优先升HTTP/2,改动小收益大;然后再考虑HTTP/3

浏览器和服务端支持情况

浏览器支持:

浏览器 HTTP/2 HTTP/3
Chrome ✅ 默认启用 ✅ 默认启用(2019年起)
Firefox ✅ 默认启用 ✅ 默认启用
Safari ✅ 默认启用 ✅ 默认启用(14.1+)
Edge ✅ 默认启用 ✅ 默认启用

主流浏览器全部支持HTTP/3,不需要做兼容处理。

服务端支持:

服务/软件 HTTP/2 HTTP/3
Nginx ⚠️ 需要编译QUIC模块(官方尚未默认内置)
Apache ⚠️ 需要mod_quic
Caddy 默认支持(最简单的HTTP/3开启方式)
Cloudflare 全网支持(CDN场景免配置)
阿里云CDN ✅ 支持开启

如果你用的是Nginx,目前最省事的方式是在前面套一层Cloudflare CDN,自动获得HTTP/3能力。自建的话Caddy是最友好的选择------一行配置搞定。


总结

三代HTTP的演进主线很清晰:

HTTP/1.1 --- 能用,但问题一堆:队头阻塞、头部冗余、连接数受限。就像一条单车道公路,车多了就堵。

HTTP/2 --- 解决了应用层的大部分问题:二进制分帧实现多路复用、HPACK压缩头部、单连接搞定所有请求。但底层还是TCP,丢包时全军覆没。像拓宽到了多车道高速,但只要有一辆车抛锚,整条路都得停。

HTTP/3 --- 彻底换了运输工具:放弃TCP改用QUIC(基于UDP),从根子上消灭了队头阻塞,加上0-RTT连接和连接迁移,移动端弱网体验大幅提升。像升级成了高铁加飞机,不仅快,还不怕堵。

不过技术选型永远要看场景。如果你的用户在国内、网络条件好,HTTP/2已经能给到90分的体验,HTTP/3那额外的10分不一定值得投入。但如果你的产品面向全球或有大量移动端用户,HTTP/3就是那个值得投入的升级点。

相关推荐
暗冰ཏོ1 小时前
CSS 超详细讲解(从基础到高级实战)
前端·css·css3·sass·scss
历程里程碑1 小时前
54 深入解析poll多路复用技术
java·linux·服务器·开发语言·前端·数据结构·c++
&&月弥2 小时前
react快速入门
前端·react.js
Revio Lab2 小时前
把 AI 生成的 HTML 当 Markdown 来管:Web-Doc 自托管文档站实践
前端·html·mcp·html文档
TechWayfarer2 小时前
IP归属地API实战指南:用IP数据云解析日志挖掘用户地域分布
大数据·开发语言·网络·python·tcp/ip
之歆2 小时前
DAY_13DOM操作完全指南DOM基础API与节点操作(上)
开发语言·前端·javascript·ecmascript
zhoumeina992 小时前
如何保证不同位置切换合成底图的渲染顺序
java·前端·javascript
海上彼尚2 小时前
Nodejs也能写Agent - 3.基础篇 - Tools 与 Tool Calling
前端·人工智能·后端·node.js
用户125758524362 小时前
GoFrame + Vue3 后台管理框架,CRUD 代码生成器一键搭 RBAC 权限系统
前端