第五章 HTTP/1.x (HTTP/1.x Protocol)
一、导读
1.1 本章学习目标
理解 HTTP/1.0 和 HTTP/1.1 的核心差异以及 HTTP/1.1 引入的关键优化
掌握 HTTP/1.1 持久连接(Keep-Alive)的工作原理和性能优势
理解 HTTP 管线化(Pipelining)的概念、限制及实践中不可用的原因
深入分析 HTTP/1.1 队头阻塞问题及其对 Web 性能的严重影响
掌握 HTTP 缓存机制(Cache-Control、ETag、Last-Modified、Expires)的完整体系
理解 HTTP 压缩(Gzip、Brotli)对传输性能的影响
掌握 HTTP Cookie 的开销及其对请求-响应性能的影响
了解 HTTP/1.x 的性能局限以及这些局限如何推动了 HTTP/2 的诞生
1.2 为什么本章如此重要
尽管 HTTP/2 和 HTTP/3 已经逐步普及,HTTP/1.1 仍然是当今互联网上使用最广泛的 HTTP 版本。根据 W3Techs 的统计,截至 2024 年,仍有超过 50% 的网站主要使用 HTTP/1.1。理解 HTTP/1.1 的工作原理和性能特征,对于优化现有 Web 应用、理解 HTTP/2 的设计动机以及进行有效的性能调优都至关重要。
HTTP/1.1 的设计可以追溯到 1997 年(RFC 2068),当时的 Web 以传输静态 HTML 文档为主,页面资源数量少、体积小。然而,现代 Web 应用的一个页面可能包含数百个资源文件(JavaScript、CSS、图片、字体等),总大小可达数 MB。HTTP/1.1 的设计假设已经远远不能适应现代 Web 的需求。
HTTP/1.1 的性能瓶颈主要体现在以下几个方面:队头阻塞导致资源只能串行传输;头部冗余导致每个请求都携带大量重复的头部信息;缺乏服务器推送能力导致客户端必须主动请求每一个资源;缓存机制虽然强大但配置复杂且容易出错。
深入理解 HTTP/1.1 的局限性,有助于开发者做出正确的技术决策:什么时候应该使用域名分片(Domain Sharding)来绕过浏览器并发连接限制?什么时候应该合并资源(Concatenation)来减少请求数量?什么时候应该升级到 HTTP/2 来从根本上解决这些问题?
1.3 核心问题引导
HTTP/1.1 的持久连接和管线化分别解决了什么问题?为什么管线化在实践中没有被广泛采用?
HTTP/1.1 的队头阻塞问题是如何产生的?它对页面加载性能有什么具体影响?
浏览器的并发连接限制是什么?域名分片(Domain Sharding)是如何绕过这个限制的?在 HTTP/2 时代还需要域名分片吗?
HTTP 缓存的强缓存和协商缓存有什么区别?Cache-Control 的 max-age、no-cache、no-store 各有什么含义?
ETag 和 Last-Modified 作为缓存验证机制各有什么优缺点?
Gzip 和 Brotli 压缩对 Web 性能有什么影响?如何选择合适的压缩级别?
HTTP Cookie 对性能有什么影响?如何减少 Cookie 的开销?
HTTP/1.x 的哪些局限性直接推动了 HTTP/2 的设计?
二、核心概念详解
2.1 HTTP/1.1 连接管理
HTTP/1.0 的默认行为是每个请求-响应对使用一个独立的 TCP 连接。请求完成后连接立即关闭。这意味着加载一个包含 10 个资源的页面需要建立 10 个 TCP 连接,每个连接都需要经历三次握手(1 RTT)和可能的 TLS 握手(2 RTT for TLS 1.2)。
HTTP/1.1 引入了持久连接(Persistent Connection,也称为 Keep-Alive)作为默认行为。一个 TCP 连接可以在多个请求-响应对之间复用,避免了重复建立连接的开销。持久连接通过 Connection: keep-alive 头部来维持。
然而,持久连接引入了队头阻塞问题——在一个连接上,响应必须按照请求的顺序返回。如果一个请求的处理时间较长(如复杂的数据库查询),后续所有请求都必须等待,即使它们的数据已经准备好。这就是 HTTP/1.1 的连接级队头阻塞。
HTTP 管线化(Pipelining)试图解决这个问题——它允许在收到前一个响应之前发送下一个请求,从而减少等待时间。但管线化有两个致命限制:响应仍然必须按顺序返回(队头阻塞依然存在);中间设备和服务器对管线化的支持不一致,导致实践中几乎不可用。所有主流浏览器都默认禁用了 HTTP 管线化。
2.2 HTTP 缓存体系
HTTP 缓存是 Web 性能优化的基石。通过合理地配置缓存策略,可以避免不必要的网络请求,显著减少页面加载时间。HTTP 缓存分为两大类:强缓存(Strong Cache)和协商缓存(Conditional Cache)。
强缓存由 Cache-Control 响应头控制。Cache-Control: max-age=31536000 表示资源在 31536000 秒(一年)内可以直接使用本地缓存,不需要向服务器发送任何请求。Cache-Control: no-store 表示不缓存任何内容。Cache-Control: no-cache 表示可以使用缓存但必须先向服务器验证(实际上走协商缓存流程)。Cache-Control: private 表示只有浏览器可以缓存,CDN 等中间缓存不能缓存。Cache-Control: public 表示任何节点都可以缓存。
协商缓存使用 ETag/If-None-Match 或 Last-Modified/If-Modified-Since 机制。服务器在响应中提供资源的 ETag(内容的哈希值)或 Last-Modified(最后修改时间)。后续请求中,浏览器携带 If-None-Match(ETag 值)或 If-Modified-Since(时间值)。如果资源未改变,服务器返回 304 Not Modified,不传输响应体;如果资源已改变,返回 200 和新内容。
合理的缓存策略是:对于不经常变化的静态资源(JavaScript、CSS、图片),使用长缓存(max-age 设为一年)并通过文件名哈希实现缓存失效;对于 HTML 文档,使用协商缓存(no-cache + ETag),确保用户总是获取最新内容。
2.3 HTTP 压缩
HTTP 压缩通过在传输前压缩响应体来减少数据量,是提升 Web 性能最直接有效的手段之一。主流的压缩算法包括 Gzip 和 Brotli。
Gzip 压缩算法:
Gzip 基于 DEFLATE 算法(LZ77 + 霍夫曼编码),压缩级别从 1(最快)到 9(最高压缩比)。
| 压缩级别 | 压缩速度 | 压缩率 | 说明 |
|---|---|---|---|
| 1 | 最快 | 较低 | 适合实时压缩 |
| 6 | 中等 | 较高 | 默认级别,性能与压缩率平衡 |
| 9 | 最慢 | 最高 | 适合预压缩静态资源 |
对于文本资源(HTML、CSS、JavaScript),Gzip 通常可以减少 60-80% 的传输大小。
Brotli 压缩算法:
Brotli 是 Google 在 2015 年推出的压缩算法,专为文本压缩优化。
| 特性 | Gzip | Brotli |
|---|---|---|
| 压缩级别数 | 9 个 | 11 个 |
| 同级压缩率对比 | 基准 | 高 15-25% |
| Brotli 级别 4 | - | 压缩率 ≈ Gzip 级别 9,速度更快 |
| 浏览器支持 | 全部 | 全部主流浏览器 |
压缩的性能影响包括两个方面:服务器端的压缩计算消耗 CPU 资源,客户端的解压缩消耗一定的处理时间。对于静态资源,可以在构建时预压缩(Pre-compression),避免运行时的 CPU 开销。对于动态内容,需要在压缩率和响应延迟之间取得平衡——过高的压缩级别会增加服务器处理时间,可能抵消传输节省的时间。
2.4 HTTP 头部开销
HTTP 头部在每次请求和响应中都会传输,而且许多头部信息是重复的。对于包含大量小资源的页面,头部开销可能占总传输量的相当比例。
典型的 HTTP 请求头包括:Host、User-Agent、Accept、Accept-Language、Accept-Encoding、Connection、Cookie 等,大小约 500-1000 字节。典型的 HTTP 响应头包括:Content-Type、Content-Length、Date、Server、Set-Cookie、Cache-Control 等,大小约 200-500 字节。
Cookie 是头部开销的主要来源。每个 Cookie 的大小限制为 4KB,而且每个请求都会携带该域名下的所有 Cookie。如果一个网站设置了大量 Cookie(如跟踪 Cookie、会话 Cookie、偏好 Cookie),每个请求的 Cookie 开销可能达到数 KB。对于包含数百个资源请求的页面,Cookie 的累积开销可能达到数百 KB。
减少头部开销的策略包括:使用 Cookie 无关的域名(如 static.example.com)托管静态资源;减少 Cookie 的大小和数量;使用 HTTP/2 的头部压缩(HPACK)来消除重复头部的冗余。
2.5 浏览器并发连接限制与域名分片
浏览器对每个域名的并发 TCP 连接数有限制。HTTP/1.1 规范建议最少 2 个,但现代浏览器通常允许 6-8 个并发连接(Chrome 为 6 个,Firefox 为 6-17 个,取决于版本和配置)。
当一个页面需要加载的资源数量超过并发连接限制时,多余的请求必须排队等待。对于包含 50 个资源的页面,如果并发连接数为 6,至少需要 9 轮才能完成所有请求(假设每个请求耗时一个 RTT)。
域名分片(Domain Sharding)是一种绕过并发连接限制的优化技术——将资源分布在多个子域名上(如 static1.example.com、static2.example.com 等),每个子域名都有独立的并发连接池。使用 4 个子域名可以将并发连接数从 6 提升到 24。
然而,域名分片有显著的代价:每个域名需要额外的 DNS 查询和 TCP/TLS 连接建立;资源无法跨域名均匀分配可能导致某些连接空闲而其他连接繁忙。在 HTTP/2 时代,域名分片反而有害——HTTP/2 的多路复用允许在单个连接上并行传输所有资源,多个连接反而增加了资源竞争和拥塞控制的开销。
三、深入分析与讨论
3.1 HTTP/1.x 性能瓶颈的系统分析
HTTP/1.x 的性能瓶颈可以从多个维度进行系统分析。连接层面:队头阻塞限制了单连接的并行性,域名分片虽然增加了并行度但引入了额外的连接建立开销。传输层面:未压缩的头部冗余在大量小请求场景下效率低下,缺乏头部压缩机制。缓存层面:虽然缓存机制功能丰富,但配置复杂且容易出错,Cookie 的无差别传输增加了不必要的开销。
这些瓶颈的综合效应是:现代 Web 页面(包含 50-100 个资源)在 HTTP/1.1 上的加载性能远不如理论带宽所允许的水平。性能瓶颈不在带宽而在延迟——每个请求至少需要一个 RTT,加上 DNS 查询、TCP 握手和 TLS 握手的开销,页面加载的大部分时间都在等待而非传输数据。
这些分析直接推动了 HTTP/2 的设计:多路复用解决队头阻塞、头部压缩减少冗余、服务器推送减少请求延迟、二进制分帧简化解析。HTTP/2 的设计目标就是消除 HTTP/1.x 的这些系统性瓶颈。
3.2 缓存策略的深度优化
HTTP 缓存策略的优化需要综合考虑资源特性、更新频率和用户体验。一个精心设计的缓存策略可以将重复访问的网络请求减少 80% 以上。
"缓存层级"策略:HTML 文档使用 Cache-Control: no-cache, must-revalidate,确保每次都验证但允许使用协商缓存;带哈希的静态资源(如 app.a1b2c3.js)使用 Cache-Control: public, max-age=31536000, immutable,实现一年缓存且不需要验证;API 响应使用 Cache-Control: private, max-age=60,允许短暂缓存但快速过期。
"缓存预热"策略:通过 Service Worker 预缓存关键资源,在用户导航到新页面时直接从缓存提供资源。这种策略可以实现"即时加载"的用户体验,但需要仔细管理缓存空间和更新策略。
"缓存失效"策略:使用文件名哈希(Content Hash)实现精确的缓存失效——文件内容变化时文件名也变化,旧缓存自动失效。结合 HTML 中的 <link> 和 <script> 标签引用更新后的文件名,实现无缝的资源更新。
3.3 从 HTTP/1.1 到 HTTP/2 的迁移考量
从 HTTP/1.1 迁移到 HTTP/2 不是简单的协议升级,还需要重新审视和优化之前为 HTTP/1.1 设计的性能策略。
需要撤销的 HTTP/1.1 优化:域名分片应该被移除——HTTP/2 的单连接多路复用使得多个域名反而增加开销;资源合并(Concatenation)应该被谨慎对待——HTTP/2 可以高效传输大量小文件,合并可能反而降低缓存效率;内联资源(Inlining)应该减少——HTTP/2 的服务器推送可以更优雅地解决关键资源的提前加载。
需要新增的 HTTP/2 优化:启用服务器推送(Server Push)预加载关键资源;利用多路复用特性,保持资源的细粒度拆分以提高缓存命中率;配置合理的流优先级,确保关键资源优先传输。
四、实践案例
4.1 案例一:HTTP 缓存策略优化
一个新闻网站的重复访问用户仍然需要下载大量资源。分析发现,该网站的 CSS 和 JavaScript 文件设置了 Cache-Control: max-age=0,导致每次访问都需要重新验证。图片资源虽然设置了 max-age=86400(一天),但没有使用文件名哈希,导致更新时无法精确失效。
优化方案:对静态资源引入构建时的文件名哈希机制(如 style.a1b2c3.css);将静态资源的 Cache-Control 设为 max-age=31536000, immutable;HTML 文档设为 no-cache;配置 ETag 作为协商缓存的后备。优化后,重复访问的网络请求从 2.5MB 降低到 150KB(仅 HTML 文档),页面加载时间从 4.2 秒降低到 1.5 秒。
4.2 案例二:HTTP/1.1 队头阻塞的性能影响
一个 Web 应用使用 HTTP/1.1 加载 30 个资源,浏览器并发连接数为 6。通过 Performance 面板分析发现,由于队头阻塞,某些连接上的慢请求(如一个需要 500ms 服务端处理的 API 请求)阻塞了同一连接上的后续资源请求。
优化方案:将关键资源(CSS、首屏 JavaScript)和非关键资源(第三方脚本、分析代码)分配到不同的域名;使用 async 和 defer 属性延迟非关键 JavaScript 的加载;将静态资源迁移到支持 HTTP/2 的 CDN。优化后,关键资源的加载不再被非关键请求阻塞,首屏渲染时间减少了 40%。
五、常见误区与注意事项
误区:HTTP 管线化可以提升性能。 虽然 HTTP 管线化在规范中是合法的,但实践中几乎所有浏览器都禁用了它。原因是中间设备(代理、防火墙、CDN)对管线化的支持不一致,可能导致响应错乱。此外,即使管线化工作正常,响应仍然必须按顺序返回,队头阻塞问题依然存在。
误区:域名分片在 HTTP/2 时代仍然有益。 域名分片是 HTTP/1.1 时代绕过并发连接限制的技巧。在 HTTP/2 中,单连接多路复用已经解决了并发问题,多个域名反而增加了 DNS 查询、TCP 连接和 TLS 握手的开销,还可能导致拥塞控制效率降低。迁移到 HTTP/2 后应该减少域名分片。
误区:Brotli 压缩级别越高越好。 Brotli 的高压缩级别(10-11)需要显著更多的 CPU 时间和内存。对于动态内容,级别 4-6 通常是最佳平衡点。级别 11 的压缩率仅比级别 6 高约 5%,但压缩时间可能增加 10 倍以上。
注意事项:Cookie 的性能影响常被忽视。 每个请求都会携带该域名的所有 Cookie,包括静态资源请求。使用 Cookie 无关的子域名托管静态资源可以显著减少请求头大小。
注意事项:Cache-Control 的优先级。 当 Cache-Control 和 Expires 同时存在时,Cache-Control 优先。no-cache 不等于"不缓存"——它表示"缓存但需要验证"。真正不缓存的是 no-store。
六、本章小结
本章全面分析了 HTTP/1.x 协议的工作原理和性能特征。HTTP/1.1 通过持久连接减少了连接建立的开销,但队头阻塞限制了单连接的并行传输能力。HTTP 缓存体系是 Web 性能优化的基石,合理配置缓存策略可以大幅减少网络请求。HTTP 压缩(Gzip/Brotli)可以显著减少传输数据量。
核心要点包括:HTTP/1.1 的队头阻塞是其最大的性能瓶颈,导致资源只能串行传输;浏览器并发连接限制(每域名 6 个)促使了域名分片技术的发展;HTTP 缓存分为强缓存和协商缓存,需要根据资源特性选择合适的策略;Cookie 的无差别传输增加了不必要的头部开销。
HTTP/1.x 的局限性直接推动了 HTTP/2 的设计。理解 HTTP/1.x 的性能瓶颈是理解 HTTP/2 创新的基础,也是在实际项目中做出正确性能优化决策的前提。
七、思考题与练习
比较 HTTP/1.0 和 HTTP/1.1 的连接管理方式,分析持久连接的优势和队头阻塞的产生原因。
设计一个完整的 HTTP 缓存策略,分别针对 HTML 文档、带哈希的静态资源、API 响应和图片资源。
解释 HTTP 管线化为什么在实践中不可用,以及浏览器和服务器面临的具体挑战。
比较 Gzip 和 Brotli 的压缩率、压缩速度和浏览器支持情况。在什么场景下应该选择哪种算法?
分析域名分片在 HTTP/1.1 和 HTTP/2 中的不同效果,解释为什么 HTTP/2 时代应该减少域名分片。
计算一个包含 50 个资源的页面在 HTTP/1.1(6 个并发连接)下的理论最小加载时间,假设每个资源的传输时间为一个 RTT(50ms)。
分析 Cookie 对 Web 性能的影响,并提出至少三种减少 Cookie 开销的策略。
使用浏览器开发者工具分析一个实际网站的 HTTP 缓存配置,评估其合理性并提出优化建议。