HTTP 队头阻塞
理解 HTTP/1.1 和 HTTP/2 中的队头阻塞问题及解决方案
问题
什么是 HTTP 队头阻塞 (Head-of-Line Blocking)?
解答
队头阻塞是指前面的请求/数据包阻塞了后面的请求/数据包。在 HTTP 协议中,存在两个层面的队头阻塞。
HTTP/1.1 的队头阻塞
HTTP/1.1 默认使用持久连接,但请求必须按顺序发送和接收:
客户端 服务器
| |
|------- 请求 A -------->|
| | (A 处理中...)
|------- 请求 B -------->| (B 必须等待)
| | (A 处理中...)
|<------ 响应 A ---------|
|<------ 响应 B ---------|
如果请求 A 处理很慢,请求 B 即使已经准备好也必须等待。
解决方案:
- 并发连接 - 浏览器对同一域名开启 6-8 个 TCP 连接
- 域名分片 - 将资源分散到多个子域名
- HTTP/2 多路复用 - 在单个连接上并行传输多个请求
HTTP/2 的队头阻塞
HTTP/2 通过多路复用解决了应用层的队头阻塞,但 TCP 层仍存在问题:
HTTP/2 帧传输(基于 TCP)
Stream 1: [帧1] [帧2] [帧3]
Stream 2: [帧1] [帧2]
Stream 3: [帧1] [帧2] [帧3]
TCP 传输序列: [1-1][2-1][3-1][1-2][2-2][3-2][1-3][3-3]
↑
如果这个包丢失
后面所有包都要等待重传
// 模拟 TCP 队头阻塞的影响
const packets = ['1-1', '2-1', '3-1', '1-2', '2-2', '3-2'];
const lostPacket = '3-1';
// TCP 必须按序交付,丢包会阻塞所有后续数据
function tcpDeliver(packets, lostIndex) {
const delivered = [];
const blocked = [];
for (let i = 0; i < packets.length; i++) {
if (i < lostIndex) {
delivered.push(packets[i]);
} else {
// 丢包后,所有包都被阻塞,等待重传
blocked.push(packets[i]);
}
}
return { delivered, blocked };
}
console.log(tcpDeliver(packets, 2));
// { delivered: ['1-1', '2-1'], blocked: ['3-1', '1-2', '2-2', '3-2'] }
HTTP/3 的解决方案
HTTP/3 使用 QUIC 协议(基于 UDP),每个流独立处理丢包:
QUIC 传输
Stream 1: [帧1] [帧2] [帧3] → 独立重传
Stream 2: [帧1] [帧2] → 不受影响
Stream 3: [帧1] ✗ [帧3] → Stream 3 等待重传,其他流继续
// QUIC 的独立流处理
const streams = {
stream1: ['帧1', '帧2', '帧3'],
stream2: ['帧1', '帧2'],
stream3: ['帧1', '帧2', '帧3']
};
// Stream 3 的帧2 丢失
function quicDeliver(streams, lostStream, lostFrame) {
const result = {};
for (const [stream, frames] of Object.entries(streams)) {
if (stream === lostStream) {
// 只有丢包的流被阻塞
result[stream] = {
delivered: frames.slice(0, lostFrame),
status: '等待重传'
};
} else {
// 其他流正常传输
result[stream] = {
delivered: frames,
status: '完成'
};
}
}
return result;
}
console.log(quicDeliver(streams, 'stream3', 1));
// stream1: { delivered: ['帧1', '帧2', '帧3'], status: '完成' }
// stream2: { delivered: ['帧1', '帧2'], status: '完成' }
// stream3: { delivered: ['帧1'], status: '等待重传' }
对比总结
| 协议 | 应用层队头阻塞 | 传输层队头阻塞 |
|---|---|---|
| HTTP/1.1 | ✗ 存在 | ✗ 存在 |
| HTTP/2 | ✓ 解决 | ✗ 存在 |
| HTTP/3 | ✓ 解决 | ✓ 解决 |
关键点
- HTTP/1.1 队头阻塞:请求必须按序处理,前面的请求阻塞后面的
- HTTP/2 多路复用解决了应用层队头阻塞,但 TCP 丢包仍会阻塞所有流
- HTTP/3 使用 QUIC 协议,每个流独立处理丢包,彻底解决队头阻塞
- 浏览器通过并发连接(6-8个)缓解 HTTP/1.1 的队头阻塞
- 高丢包网络环境下,HTTP/2 性能可能不如 HTTP/1.1 的多连接方案
目录