HTTP 协议在不断演进,从 HTTP/1.x 到 HTTP/2,现在的 HTTP/3 也在演进中。对性能的追求是 HTTP 协议演进的主要目的之一。
HTTP2
就 HTTP/2 而言,其提高性能的主要手段有连接复用、头部压缩和 Server Push。
连接复用
HTTP 协议是基于 TCP 协议的,而 TCP 协议则是面向连接的。对于 HTTP/1 的请求—响应来说,每次请求就意味着一次 TCP 连接,而 TCP 连接的耗时是比较高的,而维持大量的 TCP 连接也会占用服务器端的资源,对同一个客户端& 服务器端建立多个 TCP 连接来传输多个请求—响应显然是一种浪费。
HTTP1 还有队头阻塞问题,由于 HTTP 请求—响应是一一对应的,使用同一个连接发送多个 HTTP 请求会产生队头阻塞问题,即第二个请求需要等待第一个请求返回后才能发起。
在这种情况下,虽然多个请求可以复用同一个 TCP 连接而不是关闭重新建立连接(HTTP1 可以重复利用 TCP 连接),但是请求只能排队逐个进行,第二个请求必须等待第一个请求的响应返回后才能发起,即使它们之间不存在任何依赖关系。为了实现并行请求,客户端(浏览器)可以同时建立多个 TCP 连接来并发发送多个 HTTP 请求,但由于 TCP 连接的建立和维持需要成本,因此浏览器一般以域名为维度限制 TCP 连接的并发数,这个数字大多数是 6。
HTTP/2 对此提出了更根本的解决方案,即让 HTTP 在单个 TCP 连接内可以发送多个请求,并且可以乱序返回。
在开启 HTTP/2 的情况下,浏览器只建立了一个请求,所有的请求都是并行发起的,响应的时间也和先后无关,这样整体的响应时间得以提前。
头部压缩
HTTP 日常传输的内容可以通过在 HTTP Header 中的 Content-Encoding 来指定压缩算法进行压缩传输,然而在 HTTP/1 中,HTTPHeader 本身没有经过任何压缩,是作为纯文本传输的。随着网页内的请求越来越多,这些冗余的 Header 会形成非常大的浪费。 在这些 HTTP Header 中,最常见的占用体积的部分就是 Cookie。
Cookie 一般被限制为 4KB 左右,但对于一个复杂的页面来说,可能有数百个请求,假设每个请求都携带一个 500Byte 的 Cookie,那么 300 个请求的页面就会有 150KB 的 Header。正如上面提到的,这部分体积在 HTTP/1 下是无法压缩传输的。对于大部分的网络环境来说,上传带宽往往比下载带宽小很多,所以优化这部分体积就显得尤为重要。
为了彻底解决头部传输体积的问题,HTTP/2 引入了HPACK 头部压缩算法。
根据 Cloudflare 的数据,HPACK 头部压缩算法将线上的 Request Header 压缩了大约 76%
Server Push
Server Push 也是 HTTP/2 一个广为人知的特性,然而推出至今仍然没有得到广泛运用。
Server Push 允许服务器端直接给客户端推送内容。这样在服务器端收到 HTML 请求时,能够主动要求把接下来需要使用的资源主动推送给客户端
为了实现这种推送,服务器端需要在响应 HTML 时返回一个 PUSH_PROMISE 帧,同时把需要推送的内容和 HTML 一起返回给客户端,当客户端试图加载 CSS 时,可以根据 PUSH_PROMISE 帧的 ID 读取到对应的流
然而,由于服务器端无从判断客户端资源的缓存状况,当服务器端把内容推送给客户端后,客户端可能会发现本地已经有了/index.js 的缓存,这个时候即使存在 PUSH_PROMISE 帧客户端也会放弃。这意味着 ServerPush 同样无法有效地和本地缓存相互复用
折中方案是在服务器端通过 Cookie 猜测用户是否需要 Server Push,仅针对新用户开启 Server Push,而老用户仍然使用正常的请求链路,如图 10-16 所示。之所以叫猜测,是因为有 Cookie 不代表用户一定有缓存,没有 Cookie 也不代表一定没有缓存
综上,Server Push 存在着:
- 缓存复用问题
Server Push 无法有效地和本地缓存相互复用,因此,可能会出现推送到一半发现已经存在缓存的情况
- 无法从第三方推送
Server Push 的本质是服务器端在响应 HTML 时把静态资源一起从服务器端响应回来。在一般情况下,由于服务器端离用户的物理距离较远,因此通常把静态资源托管在第三方的 CDN 上。而 Server Push 的原理决定了它无法从第三方服务器(CDN 节点)进行推送,为了尽快开始发送内容而放弃 CDN 在静态资源托管性能上的优势,往往是得不偿失的。
HTTP3
HTTP/3 是网络基本协议持续改进的成果。它建立在 HTTP/2 的成功基础之上,HTTP/2 引入了多路复用、标头压缩和服务器推送等功能,但仍受到底层 TCP 的限制。
HTTP/3 通过 QUIC(Quick UDP Internet Connection,快速 UDP 互联网连接) 解决了这些限制,QUIC 有望实现更快、更可靠的连接,尤其是在性能不稳定的网络(例如移动和无线网络)上。
QUIC 是一种无连接协议,以 UDP 为基础,避免了与 TCP 连接相关的慢启动和多次往返。它集成了传输层安全性(TLS)以实现端到端加密,简化了协议堆栈并增强了安全性。QUIC 还具有先进的拥塞控制算法和机制,可处理数据包丢失而不会造成重大延迟
优点:
- 更快的页面加载时间,更快的连接建立速度
- 增强的安全功能:QUIC 中的传输层安全性无缝且强大
- 提高连接可靠性:HTTP/3 和 QUIC 通过最小化延迟和优化数据包传输显着增强了连接可靠性
- 高效资源利用:HTTP/3 和 QUIC 通过最小化冗余数据传输和增强拥塞控制机制来优化资源利用率
可用性:
下图内容来自 ai 回答:
Web 浏览器对 HTTP/3 的支持非常广泛。大多数主流浏览器(包括 Chrome、Firefox、Safari 和 Edge)都已支持它。这种全面采用确保最终用户可以体验 HTTP/3 的增强性能和降低的延迟。但是,要充分利用 HTTP/3 的优势,您的服务器基础设施和 CDN 服务必须符合 HTTP/3 标准。
小结
整个 HTTP 协议的升级迭代过程,其实在很大程度上是为性能服务的。
从 HTTP/1 到 HTTP/2,笔者都试图用多路复用、头部压缩等解决 HTTP/1 在性能方面的缺陷。之所以在 HTTP/2 之后仍然有 HTTP/3 这样的升级,是因为到了 HTTP/2 之后,HTTP 协议在性能方面的不足主要都是由 TCP 协议带来的。
基于 TCP 协议意味着也会受到 TCP 协议的限制,TCP 协议会保证传输顺序,但并不理解自身传输的内容(HTTP)。当出现丢包时,即使有独立的 HTTP 请求内容传输完毕,也会因为等待丢包重传而无法交付给应用层使用。所以,为了摆脱 TCP 协议的限制,HTTP/3 基于 UDP 协议实现。
从 HTTP 协议的升级过程可以看出,网络协议与性能的联系是非常紧密的。协议的性能特性对开发人员来说也不是无感知的,理解了协议特性背后的原理,才能选择合适的优化手段。
参考资料:
《前端性能揭秘》(2022-电子工业出版社)-第 10 章《HTTP/2、HTTP/3 和性能》