连接方式
- HTTP1.0:默认使用短连接,每个请求都需要新建 TCP 连接,请求完成后关闭连接。
- HTTP1.1:默认使用长连接,允许在一个 TCP 连接上发送多个请求和响应,减少了建立和断开 TCP 连接的开销。
- HTTP2.0:基于 TCP 协议,一个 TCP 连接能处理多个 HTTP 请求,实现了多路复用,多个请求和响应可以在一个 TCP 连接上交错发送。
- HTTP3.0:不依赖 TCP,基于 QUIC 协议,集成了 TLS 加密、流量控制、多路复用等功能,降低了网络延迟。
数据格式
- HTTP1.0:基于文本的协议。
- HTTP1.1:基于文本的协议。
- HTTP2.0:采用二进制格式,实现方便且健壮。
- HTTP3.0:基于 QUIC 协议,其数据格式与 HTTP2.0 有所不同,在传输层对数据进行了优化。
功能特性
- HTTP1.0:不支持多路复用、头部压缩、服务端推送、请求优先级和断点续传等功能,请求头不支持 Host 头域。
- HTTP1.1:支持断点续传,增加了更多的请求头和响应头,如身份认证、状态管理和 Cache 缓存等。支持请求的流水线处理,但仍存在 “队头阻塞” 问题。
- HTTP2.0:支持服务端推送、流量控制和请求优先级,支持头部压缩,进一步减少了请求和响应的大小。
- HTTP3.0:支持头部压缩、服务端推送,采用 TLS1.3 作为默认的安全层协议,提供更强的安全性。解决了 HTTP2.0 中前一个 stream 丢包导致后一个 stream 被阻塞的问题。
每次发展解决的问题如下:
- HTTP1.0 到 HTTP1.1:解决了 HTTP1.0 中短连接导致的性能问题,通过长连接减少了 TCP 连接的重复建立和断开所造成的额外开销。增加了缓存控制机制,引入了请求头部压缩和虚拟主机的概念,支持断点续传,提高了网络利用率和性能。
- HTTP1.1 到 HTTP2.0:解决了 HTTP1.1 中的 “队头阻塞” 问题,通过多路复用和二进制分帧特性,实现了真正的并行传输,提高了连接的利用率。同时,支持服务端推送和头部压缩,进一步优化了性能和用户体验。
- HTTP2.0 到 HTTP3.0:解决了 HTTP2.0 中基于 TCP 协议存在的一些问题,如 TCP 的拥塞控制和重传机制可能导致的延迟和性能瓶颈。HTTP3.0 采用基于 UDP 的 QUIC 协议,降低了网络延迟,解决了流的阻塞问题,在移动网络环境下表现更优,并且提供了更好的连接迁移能力和安全性。
队头阻塞
一、单客户端单连接场景:排队机制导致自身阻塞
假设只有一个客户端通过一条连接与服务器通信,且服务器按 ** 先来先服务(FIFO)** 处理请求:
- 客户端发送请求 R1(如复杂计算),服务器开始处理 R1(耗时 10 秒)。
- 处理 R1 期间,客户端又发送请求 R2(如简单查询),但服务器只能按顺序处理 ——R2 必须等待 R1 完成后才会被处理。
- 客户端的行为:
- 发送 R1 后,会阻塞等待 R1 的响应,期间无法发送 R2(若客户端是单线程且同步发送请求)。
- 若客户端支持异步发送(如多线程),R2 会进入服务器的请求队列,但服务器仍需先完成 R1 才能处理 R2。
- 结果:客户端等待 R1 的 10 秒内,无法获得 R2 的响应,即使 R2 本身只需 1 秒处理。
二、多客户端多连接场景:服务器资源被抢占
当多个客户端同时连接服务器时,服务器的共享资源(如线程池、I/O 通道)会被占用,导致排队和阻塞:
场景示例:
- 服务器使用单线程 + 队列处理所有客户端请求(类似早期 HTTP/1.1 服务器)。
- 客户端 A发送请求 R1(耗时 10 秒,占用服务器唯一线程)。
- 客户端 B同时发送请求 R2(耗时 1 秒),但 R2 需进入队列,等待 R1 处理完成。
- 客户端 C发送请求 R3(耗时 0.5 秒),同样需等待 R1 和 R2 处理。
关键问题:
- 服务器线程被 R1 独占:即使 R2 和 R3 很简单,也无法绕过 R1 优先执行。
- 客户端 B 和 C 的等待时间:
- 客户端 B 等待时间 = R1 耗时(10 秒) + R2 耗时(1 秒) = 11 秒。
- 客户端 C 等待时间 = 10 秒 + 1 秒 + 0.5 秒 = 11.5 秒。
- 资源浪费:R1 可能在等待 I/O(如数据库查询)时阻塞线程,但服务器仍无法利用这段时间处理 R2/R3(同步阻塞模型)。
三、核心矛盾:排队机制与资源利用率的冲突
1. 顺序处理的局限性
- 优点:实现简单,保证请求顺序。
- 缺点:一个慢请求会拖慢所有后续请求,无论它们是否来自同一客户端。
2. 资源竞争的影响
- 若服务器线程池大小有限(如 10 个线程),10 个慢请求会占满所有线程,后续请求全部排队,导致新请求积压甚至超时。
- 客户端可能因超时而重复发送请求,进一步加剧服务器负载(恶性循环)。
四、为什么 HTTP/1.1 的队头阻塞更严重?
- 单连接单请求:同一连接同一时间只能处理一个请求,多个请求需排队(如浏览器加载网页时,图片、CSS、JS 需按顺序加载)。
- 示例:
- 网页需加载 10 个资源,第 1 个资源是大文件(耗时 10 秒),其余 9 个资源每个耗时 1 秒。
- 总耗时 = 10 秒 + 9×1 秒 = 19 秒,即使服务器能并行处理,HTTP/1.1 也强制排队。
- 对比 HTTP/2:通过多路复用允许同一连接并行传输多个请求,避免排队阻塞。
五、总结:客户端等待的本质原因
- 服务器处理能力不足:单个请求耗时过长,或资源被低效占用(如同步阻塞 I/O)。
- 排队机制的强制性:顺序处理下,慢请求阻塞快请求,导致 “请求积压→队列变长→等待时间累加”。
- 多客户端竞争资源:多个慢请求同时到达时,服务器资源(线程、连接)被耗尽,新请求无法及时处理。
HTTP2.0并发传输
客户端行为:
通过单一 TCP 连接,向服务器发送 4 个 Stream 的 HEADERS 帧(每个 Stream 对应一个请求 Message),Frame 交错传输(如 Stream1→Stream2→Stream3→Stream4)。
2. 服务器并发处理请求(非阻塞模式)
HTTP/2 服务器采用异步非阻塞模型(如事件驱动 + 线程池),处理逻辑如下:
- 处理 Stream4(2 秒):最快完成,立即生成响应 Message(包含 HEADERS 和 DATA 帧)。
- 处理 Stream3(3 秒):次之,生成响应 Message。
- 处理 Stream2(5 秒):继续处理,生成响应 Message。
- 处理 Stream1(10 秒):最慢,最后生成响应 Message。
关键机制:
- 服务器在处理 Stream1 的耗时操作时(如等待数据库查询),不会阻塞其他 Stream 的处理(通过异步 I/O 或多线程)。
- 每个响应 Message 通过对应的 Stream ID 返回,不同 Stream 的响应可以乱序到达客户端。
3. 客户端接收响应(按 Stream ID 重组)
客户端按以下顺序接收响应帧(示例):
- Stream4 的响应帧(HEADERS+DATA)→ 重组为消息 4 的响应(耗时 2 秒)。
- Stream3 的响应帧 → 重组为消息 3 的响应(耗时 3 秒)。
- Stream2 的响应帧 → 重组为消息 2 的响应(耗时 5 秒)。
- Stream1 的响应帧 → 重组为消息 1 的响应(耗时 10 秒)。
客户端体验:
- 消息 4 和 3 的响应在短时间内返回,用户无需等待消息 1 的 10 秒耗时。
- 整体耗时由最慢的请求(消息 1)决定,但其他请求的响应已提前完成,提升了用户感知的 “响应速度”。
四、核心机制:为什么 HTTP/2 能并发处理?
- 多路复用:
多个 Stream 共享同一 TCP 连接,帧可交错传输,服务器通过 Stream ID 区分请求。 - 异步非阻塞:
服务器在处理耗时请求时(如 I/O 等待),通过异步机制释放线程资源,处理其他请求。 - 并行响应:
响应无需按请求顺序返回,客户端按 Stream ID 独立组装每个 Message。
五、错误假设的问题:“封装多个请求到一个 Message”
若强行将多个请求放入一个 Message,会导致:
- 协议解析失败:HTTP 服务器无法识别同一 Message 中的多个请求。
- 队头阻塞加剧:单个 Message 必须整体处理,多个请求被迫串行执行。
- 违反规范:HTTP 协议要求 Message 必须是完整的请求或响应,不可包含多个逻辑操作。
六、总结:HTTP/2 的并发本质
- 单位:每个请求 / 响应是独立的 Message,属于唯一的 Stream。
- 传输:多个 Stream 的 Frame 在 TCP 连接中交错传输,实现 “单连接多请求”。
- 处理:服务器异步处理每个 Stream,快请求的响应可优先返回,慢请求不阻塞其他操作。
通过这种设计,HTTP/2 实现了 “请求并发发送、响应乱序接收、资源高效利用”,彻底解决了 HTTP/1.1 的队头阻塞问题。
HTTp2.0队头阻塞问题
HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。这样在一个 TCP 连接中的所有的 HTTP 请求都必须等待这个丢了的包被重传回来。