《HTTP/2 基础教程》 阅读摘要(下)

本文涉及的产品
.cn 域名,1个 12个月
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
简介: 最近粗线了不少 HTTP2 相关的帖子和讨论,感觉新一轮的潮流在形成,所以最近找了本 HTTP2 相关书籍做知识储备,刚好记成笔记以备后询 ~如果希望获取本书的 PDF 资源,可以关注文末二维码加微信群找群主要~这本书本身不错,缺点就是翻译的有点蹩脚,另外因为是 2017 年出的书,所以有些内容时效性不太好,比如关于 Chrome 的部分,所以我根据 Chrome 的官方文档增加了点内容 😅

5. HTTP/2 协议


本章将全面探讨 HTTP/2 的底层工作原理,深入到数据层传输的帧及其通信方式。


5.1 HTTP/2 分层


HTTP/2 大致可以分为两部分


  1. 分帧层 即 h2 多路复用能力的核心部分,主要目的还是传输 HTTP
  2. 数据或 http 层 其中包含传统上被认为是 HTTP 及其关联数据的部分,向后兼容 HTTP/1.1


h2 有些特点需要关注一下:


  1. 二进制协议 :h2 的分帧层是基于帧的二进制协议,这方便了机器解析,但是肉眼识别比较困难
  2. 首部压缩 :仅使用二进制协议还不够,h2 的首部还会被深度压缩。这将显著减少传输中的冗余字节
  3. 多路复用 :在调试工具里查看基于 h2 传输的连接的时候,你会发现请求和响应交织在一起
  4. 加密传输 :线上传输的绝大部分数据是加密过的,所以在中途读取会更加困难


5.2 连接


与完全无状态的 h1 不同的是,h2 把它所承载的帧(frame)和流(stream)共同依赖的连接层元素捆绑在一起,其中既包含连接层设置也包含首部表。也就是说,与之前的 HTTP 版本不同,每个 h2 连接都有一定的开销。之所以这么设计,是考虑到收益远远超过其开销。


在连接的时候,HTTP/2 提供两种协议发现的机制:


  1. 在连接不加密的情况下,客户端会利用 Upgrade 首部来表明期望使用 h2。如果服务器也可以支持 h2,它会返回一个101 Switching Protocols(协议转换)响应。这增加了一轮完整的请求-响应通信
  2. 如果连接基于 TLS,情况就不同了。客户端在 ClientHello 消息中设置 ALPN (Application-Layer Protocol Negotiation,应用层协议协商)扩展来表明期望使用 h2 协议,服务器用同样的方式回复。如果使用这种方式,那么 h2 在创建 TLS 握手的过程中完成协商,不需要多余的网络通信。


在协议制定过程中,很早就把小数点去掉了,这表明未来的 HTTP 版本不能保证语义的向后兼容,也就是说只有 HTTP/2 没有 HTTP/2.0、HTTP/2.2


5.3 帧


HTTP/2 是基于帧(frame)的协议,采用分帧是为了将重要信息都封装起来,让协议的解析方可以轻松阅读、解析并还原信息。 相比之下,h1 不是基于帧的,而是以文本分隔。所以解析 h1 的请求或响应可能会出现以下问题:


  1. 一次只能处理一个请求或响应,完成之前不能停止解析
  2. 无法预判解析需要多少内存。这会带来一系列问题:你要把一行读到多大的缓冲区里;如果行太长会发生什么;应该增加并重新分配内存,还是返回 400 错误


从另一方面来说,有了帧,处理协议的程序就能预先知道会收到什么。下图是一个 HTTP/2 帧的结构


微信截图_20220427164950.png


前 9 个字节对于每个帧是一致的。解析时只需要读取这些字节,就可以准确地知道在整个帧中期望的字节数。其中每个字段的说明如下


名称 长度 描述
Length 3 字节 表示帧负载的长度(取值范围为 2^14~2^24-1 字节)。 请注意,214 字节是默认的最大帧大小,如果需要更大的帧,必须在 SETTINGS 帧中设置
Type 1 字节 当前帧类型
Flags 1 字节 具体帧类型的标识
R 1 位 保留位,不要设置,否则可能带来严重后果
Stream Identifier 31 位 每个流的唯一 ID
Frame Payload 长度可变 真实的帧内容,长度是在 Length 字段中设置的


相比依靠分隔符的 h1,h2 还有另一大优势:如果使用 h1 的话,你需要发送完上一个请求或者响应,才能发送下一个;由于 h2 是分帧的,请求和响应可以交错甚至多路复用。多路复用有助于解决类似队头阻塞的问题。


h2 有十种不同的帧类型:


名称 ID (Type) 描述
DATA 0x0 数据帧,传输流的核心内容
HEADERS 0x1 报头帧,包含 HTTP 首部,和可选的优先级参数
PRIORITY 0x2 优先级帧,指示或者更改流的优先级和依赖
RST_STREAM 0x3 流终止帧,允许一端停止流(通常由于错误导致的)
SETTINGS 0x4 设置帧,协商连接级参数
PUSH_PROMISE 0x5 推送帧,提示客户端,服务器要推送些东西
PING 0x6 PING 帧,测试连接可用性和往返时延(RTT)
GOAWAY 0x7 GOAWAY 帧,告诉另一端,当前端已结束
WINDOW_UPDATE 0x8 窗口更新帧,协商一端将要接收多少字节(用于流量控制)
CONTINUATION 0x9 延续帧,用以扩展 HEADER 数据块


扩展帧 :HTTP/2 内置了名为扩展帧的处理新的帧类型的能力。依靠这种机制,客户端和服务器的实现者可以实验新的帧类型,而无需制定新协议。按照规范,任何客户端不能识别的帧都会被丢弃,所以网络上新出现的帧就不会影响核心协议。当然,如果你的应用程序依赖于新的帧,而中间代理会丢弃它,那么可能会出现问题。


5.4 流


HTTP/2 规范中的流(stream):HTTP/2 连接上独立的、双向的帧序列交换。你可以将流看作在连接上的一系列帧,用来传输一对请求/响应消息。如果客户端想要发出请求,它会开启一个新的流,服务器也在这个流上回复。这与 h1 的请求、响应流程类似,区别在于,因为有分帧,所以多个请求和响应可以交错,而不会互相阻塞。流 ID(帧首部的第 6~9 字节)用来标识帧所属的流。


客户端到服务器的 h2 连接建立之后,通过发送 HEADERS 帧来启动新的流,如果首部需要跨多个帧,可能还发会送 CONTINUATION 帧。


5.4.1 消息


HTTP 消息泛指 HTTP 请求或响应。一个消息至少由 HEADERS 帧(用来初始化流)组成,并且可以另外包含 CONTINUATIONDATA 帧,以及其他的 HEADERS 帧。 下图是普通 GET 请求的示例流程


微信截图_20220427164958.png


POST 和 GET 的主要差别之一就是 POST 请求通常包含客户端发出的大量数据。下图是 POST 消息对应的各帧可能的样子


微信截图_20220427165002.png


h1 的请求和响应都分成消息首部和消息体两部分;与之类似,h2 的请求和响应分成 HEADERS 帧和 DATA 帧。HTTP/1 和 HTTP/2 消息的下列差别是需要注意


  1. 一切都是header :h1 把消息分成请求/状态行、首部两部分。h2 取消了这种区分,并把这些行变成了魔法伪首部
  2. 没有分块编码(chunked encoding) :只有在无法预先知道数据长度的情况下向对方发送数据时,才会用到分块。在使用帧作为核心协议的 h2 里,不再需要分块
  3. 不再有101的响应 :Switching Protocol 响应是 h1 的边缘应用。它如今最常见的应用可能就是用以升级到 WebSocket 连接。ALPN 提供了更明确的协议协商路径,往返的开销也更小


5.4.2 流量控制


h2 的新特性之一是基于流的流量控制。h1 中只要客户端可以处理,服务端就会尽可能快地发送数据,h2 提供了客户端调整传输速度的能力,服务端也同样可以调整传输的速度。


WINDOW_UPDATE 帧用于执行流量控制功能,可以作用在单独某个流上(指定具体 Stream Identifier)也可以作用整个连接 (Stream Identifier 为 0x0),只有 DATA 帧受流量控制影响。初始化流量窗口后,发送多少负载,流量窗口就减少多少,如果流量窗口不足就无法发送,WINDOW_UPDATE 帧可以增加流量窗口大小。流建立的时候,窗口大小默认 65535(2^16-1)字节。


5.4.3 优先级


流的最后一个重要特性是依赖关系。


现代浏览器会尽量以最优的顺序获取资源,由此来优化页面性能。在没有多路复用的时候,在它可以发出对新对象的请求之前,需要等待前一个响应完成。有了 h2 多路复用的能力,客户端就可以一次发出所有资源的请求,服务端也可以立即着手处理这些请求。

由此带来的问题是,浏览器失去了在 h1 时代默认的资源请求优先级策略。假设服务器同时接收到了 100 个请求,也没有标识哪个更重要,那么它将几乎同时发送每个资源,次要元素就会影响到关键元素的传输。


h2 通过流的依赖关系来解决上面这个问题。通过 HEADERS 帧和 PRIORITY 帧,客户端可以明确地和服务端沟通它需要什么,以及它需要这些资源的顺序。这是通过声明依赖关系树和树里的相对权重实现的。


  1. 依赖关系 为客户端提供了一种能力,通过指明某些对象对另一些对象有依赖,告知服务器这些对象应该优先传输
  2. 权重 让客户端告诉服务器如何确定具有共同依赖关系的对象的优先级


流可以被标记为依赖其他流,所依赖的流完成后再处理当前流。每个依赖 (dependency) 后都跟着一个权重 (weight),这一数字是用来确定依赖于相同的流的可分配可用资源的相对比例。


其他时候也可以通过 PRIORITY 帧调整流优先级。


设置优先级的目的是为了让端点表达它所期望对端在并发的多个流之间如何分配资源的行为。更重要的是,当发送容量有限时,可以使用优先级来选择用于发送帧的流。


5.5 服务端推送


升单个对象性能的最佳方式,就是在它被用到之前就放到浏览器的缓存里面。这正是 h2 服务端推送的目的。


5.5.1 推送对象


如果服务器决定要推送一个对象(RFC 中称为『推送响应』),会构造一个 PUSH_PROMISE 帧。这个帧有很多重要属性:


  1. PUSH_PROMISE 帧首部中的流 ID (Promised Stream ID)用来响应相关联的请求。推送的响应一定会对应到客户端已发送的某个请求。如果浏览器请求一个主体 HTML 页面,如果要推送此页面使用的某个 JavaScript 对象,服务器将使用请求对应的流 ID 构造 PUSH_PROMISE 帧。
  2. PUSH_PROMISE 帧的首部块与客户端请求推送对象时发送的首部块是相似的。所以客户端有办法放心检查将要发送的请求。
  3. 被发送的对象必须确保是可缓存的。
  4. :method 首部的值必须确保安全。安全的方法就是幂等的那些方法,这是一种不改变任何状态的好办法。例如,GET 请求被认为是幂等的,因为它通常只是获取对象,而 POST 请求被认为是非幂等的,因为它可能会改变服务器端的状态。
  5. 理想情况下,PUSH_PROMISE 帧应该更早发送,应当早于客户端接收到可能承载着推送对象的 DATA 帧。假设服务器要在发送 PUSH_PROMISE 之前发送完整的 HTML,那客户端可能在接收到 PUSH_PROMISE 之前已经发出了对这个资源的请求。h2 足够健壮,可以优雅地解决这类问题,但还是会有些浪费。
  6. PUSH_PROMISE 帧会指示将要发送的响应所使用的流 ID


如果客户端对 PUSH_PROMISE 的任何元素不满意,就可以按照拒收原因选择重置这个流(使用 RST_STREAM),或者发送 PROTOCOL_ERROR (在 GOAWAY 帧中)。常见的情况是缓存中已经有了这个对象。


假设客户端不拒收推送,服务端会继续进行推送流程,用 PUSH_PROMISE 中指明 ID 对应的流来发送对象


微信截图_20220427165008.png


5.5.2 选择要推送的资源


如果服务器接收到一个页面的请求,它需要决定是推送页面上的资源还是等客户端来请求。决策的过程需要考虑到如下方面


  1. 资源已经在浏览器缓存中的概率
  2. 从客户端看来,这些资源的优先级 (参见 5.4.3 节)
  3. 可用的带宽,以及其他类似的会影响客户端接收推送的资源


如果服务器选择正确,那就真的有助于提升页面的整体性能,反之则会损耗页面性能。


5.6 首部压缩


现代网页平均有很多请求,这些请求之间几乎没有新的的内容,这是极大的浪费。


首部列表 (Header List) 是零个或多个首部字段 (Header Field) 的集合。当通过连接传送时,首部列表通过压缩算法(即下文 HPACK) 序列化成首部块 (Header Block),不用 GZIP 是因为它有泄漏加密信息的风险。HPACK 是种表查找压缩方案,它利用霍夫曼编码获得接近 GZIP 的压缩率。


然后,序列化的首部块又被划分成一个或多个叫做首部块片段 (Header Block Fragment) 的字节序列,并通过 HEADERSPUSH_PROMISE,或者 CONTINUATION 帧进行有效负载传送。


假设客户端按顺序发送如下请求首部:


Header1: foo
Header2: bar
Header3: bat
复制代码


当客户端发送请求时,可以在首部数据块中指示特定首部及其应该被索引的值。它会创建一张表:


索引 首部名称
62 Header1 foo
63 Header2 bar
64 Header3 bat


如果服务端读到了这些请求首部,它会照样创建一张表。客户端发送下一个请求的时候, 如果首部相同,它可以直接发送:62 63 64 ,服务器会查找先前的表格,并把这些数字还原成索引对应的完整首部。首部压缩机制中每个连接都维护了自己的状态。

HPACK 的实现比这个要复杂得多,比如:


  1. 请求端和响应端各维护了两张表格。其中之一是动态表,创建方法和上面差不 多。另一张是静态表,它由 61 个最常见的首部的键值组合而成。例如 :method: GET 在静态表中索引为 2。按规定,静态表包含 61 个条目,所以上例索引编号从 62 开始。
  2. 关于字段如何索引,有很多控制规则:
  1. 发送索引编号和文本值仅发送文本值,不对它们进行索引(对于一次性或敏感首部)
  2. 发送索引的首部名,值用文本表示,但不进行索引处理(如:path: /foo.html,其值每次都不同)
  3. 发送索引过的首部名和值(如上例中的第二个请求)
  1. 使用打包方案的整数压缩,以实现极高的空间效率
  2. 利用霍夫曼编码表进一步压缩字符串


5.7 线上传输


线上传输的 h2 信息是经过压缩的二进制数据。


一个简单的GET请求


下面是一个简单的 h2 的 get 请求


:authority: www.akamai.com
:method: GET
:path: /
:scheme: https
accept: text/html,application/xhtml+xml,...
accept-language: en-US,en;q=0.8
cookie: sidebar_collapsed=0; _mkto_trk=...
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Macintosh;...
复制代码


下面是 h2 的一个响应


:status: 200
cache-control: max-age=600
content-encoding: gzip
content-type: text/html;charset=UTF-8
date: Tue, 31 May 2016 23:38:47 GMT
etag: "08c024491eb772547850bf157abb6c430-gzip"
expires: Tue, 31 May 2016 23:48:47 GMT
link: <https://c.go-mpulse.net>;rel=preconnect
set-cookie: ak_bmsc=8DEA673F92AC...
vary: Accept-Encoding, User-Agent
x-akamai-transformed: 9c 237807 0 pmb=mRUM,1
x-frame-options: SAMEORIGIN
复制代码


在这个响应中,服务器表示请求已成功受理(状态码 200),设置了 cookie(cookie 首部),表示返回的内容使用 gzip 压缩(content-encoding 首部)


6. HTTP/2性能


HTTP/2 大部分情况下传输 web 页面比 HTTP/1.1 快。


  1. 对于包含很多小型资源的页面,h2 加载页面的时间比 h1 更短。这是因为 h1 下(有 6 个 TCP 连接)服务器只能并行发送 6 个资源(由于队头阻塞),而 h2 下多个流(stream)可以共用连接。进一步说,随着网络条件变差,h1 和 h2 下页面加载时间(PLT)都会增加;但是 h2 由于单连接,如果唯一的连接发生了丢包,所有工作都会受影响。
  2. 对于包含少量大型资源的页面,在所有网络条件下,h1 性能上都比 h2 表现要好。这个多少令人感到意外的结果是初始拥塞窗口导致的。如果开启 6 个连接,h1 的初始拥塞窗口大小实际上是 h2 的 6 倍。因此在会话开始阶段,h2 的连接窗口尚未增长到最佳值,但 h1 早就能更快地传输更多数据了。这个问题目前仍在解决,因为它导致初始拥塞窗口对 h2 而言太小,然而对 h1 而言又太大。 此外,h2 比 h1 更容易受丢包的影响。
  3. 对于包含一些极大型资源的 Web 页面,两者没有任何差异。h2 的初始拥塞窗口劣势被整体下载时长掩盖了,多路复用此时也不再具有优势。


6.1 客户端实现


网络条件相同,使用不同浏览器客户端,同样的网站页面加载性能可能差别很大。


  1. 协议的具体实现很重要
  2. 并非所有请求在任何情况下都会从 HTTP/2 受益,即便如此,URL 使用 h2 后性能提升的比例也依旧高于下降的比例


6.2 延迟


延迟是指数据包从一个端点到另一个端点所花的时间。有时,它也表示数据包到达接收方然后返回发送方所需的时间,又称为往返时延(RTT),长度一般以毫秒计。


影响延迟的因素众多,但有两个是最重要的:端点间的距离,以及所用传输介质


两点之间的网线不会是笔直的,另外各种网关、路由器、交换机以及移动基站等(也包括服务器应用本身)都会增加延迟


6.3 丢包


如果网络中传输的数据包没有成功到达目的地,就会发生丢包,这通常是由网络拥堵造成的。


频繁丢包会影响 h2 的页面传输,主要是因为 h2 开启单一 TCP 连接,每次有丢包/拥堵时,TCP 协议就会缩减 TCP 窗口。


如果 TCP 流上丢了一个数据包,那么整个 h2 连接都会停顿下来,直到该数据包重发并被接收到。


6.4 服务端推送


服务端推送让服务器具备了在客户端请求之前就推送资源的能力。 测试表明,如果合理使用推送,页面渲染时间可以减少 20%~50%。


然而,推送也会浪费带宽,这是因为服务端可能试图推送那些在客户端已经缓存的资源,导致客户端收到并不需要的数据。客户端确实可以发送 RST_STREAM 帧来拒绝服务器的 PUSH_PROMISE 帧,但是 RST_STREAM 并不会即刻到达,所以服务器还是会发送一些多余的信息。


如果用户第一次访问页面时,就能向客户端推送页面渲染所需的关键 CSS 和 JS 资源,那么服务端推送的真正价值就实现了。不过,这要求服务器端实现足够智能,以避免『推送承诺』(push promise)与主体 HTML 页面传输竞争带宽。


理想情况下,服务端正在处理 HTML 页面主体请求时才会发起推送。有时候,服务端需要做一些后台工作来生成 HTML 页面。这时候服务端在忙,客户端却在等待,这正是开始向客户端推送所需资源的绝佳时机。


微信截图_20220427165016.png


6.5 首字节时间


首字节时间(TTFB)用于测量服务器的响应能力。是从客户端发起 HTTP 请求到客户端浏览器收到资源的第一个字节所经历的时间。由 socket 连接时间、发送 HTTP 请求所需时间、收到页面第一个字节所需时间组成。


h1 中,客户端针对单个域名在每个连接上依次请求资源,而且服务器会按序发送这些资源。客户端只有接收了之前请求的资源,才会再请求剩下的资源,服务器接着继续响应新的资源请求。这个过程会一直重复,直到客户端接收完渲染页面所需的全部资源。


与 h1 不同,通过 h2 的多路复用,客户端一旦加载了 HTML,就会向服务器并行发送大量请求。相比 h1,这些请求获得响应的时间之和一般会更短;但是因为是请求是同时发出的,而单个请求的计时起点更早,所以 h2 统计到的 TTFB 值会更高。


HTTP/2 比 h1 确实做了更多的工作,其目的就是为了从总体上提升性能。下面是一些 h1 没有,但 h2 实现了的


  1. 窗口大小调节
  2. 依赖树构建
  3. 维持首部信息的静态 / 动态表
  4. 压缩 / 解压缩首部
  5. 优先级调整(h2 允许客户端多次调整单一请求的优先级)
  6. 预先推送客户端尚未请求的数据流


下图是使用 h1 和 h2 加载同一个页面的加载时序对比,总体来说 h2 体验更好


微信截图_20220427165403.png


6.6 第三方资源


许多网站会使用各种统计、跟踪、社交以及广告平台,就会引入各种第三方的资源。


  1. 第三方请求往往通过不同域名发送;由于浏览器需要解析 DNS、建立 TCP 连接、协商 TLS,这将严重影响性能;
  2. 因为第三方资源在不同域名下,所以请求不能从服务端推送、资源依赖、请求优先级等 h2 特性中受益。这些特性仅是为请求相同域名下的资源设计的;
  3. 你无法控制第三方资源的性能,也无法决定它们是否会通过 h2 传输;


6.7 HTTP/2反模式


h1 下的一些性能调优办法在 h2 下会起到反作用。下面列出了一些用于优化 h1 请求的常用技巧,并标注了 h2 方面的考虑。


名称 描述 备注
资源合并 把多个文件(JavaScript、CSS) 合成一个文件,以减少 HTTP 请求 在 HTTP/2 下这并非必要,因为请求的传输字节数和时间成本更低,虽然这种成本仍然存在
极简化 去除 HTML、JavaScript、CSS 这类文件中无用的代码 很棒的做法,在 HTTP/2 下也要保留
域名拆分 把资源分布到不同的域名上面去,让浏览器利用更多的 socket 连接 HTTP/2 的设计意图是充分利用单个 socket 连接,而拆分域名会违背这种意图。建议取消域名拆分,但请注意本表格之后的附注框会介绍这个问题相关的各种复杂情况
禁用 cookie 的域名 为图片之类的资源建立单独的域名,这些域名不用 cookie,以尽可能减少请求尺寸 应该避免为这些资源单独设立域名(参见域名拆分),但更重要的是,由于 HTTP/2 提供了首部压缩,cookie 的开销会显著降低
生成精灵图 把多张图片拼合为一个文件,使用 CSS 控制在 Web 页面上展示的部分 与极简化类似,只不过用 CSS 实现这种效果的代价高昂;不推荐在 HTTP/2 中使用


6.7.1 生成精灵图和资源合并/内联


精灵图(spriting)是指把很多小图片拼合成一张大图,这样只需发起一个请求就可以覆盖多个图片元素。在 HTTP/2 中,针对特定资源的请求不再是阻塞式的,很多请求可以并行处理;就性能而言,生成精灵图已失去意义,因为多路复用和首部压缩去掉了大量的请求开销。


与之类似,小的文本资源,例如 JS 和 CSS,会依照惯例合并成一份更大的资源,或者直接内嵌在主体 HTML 中,这也是为了减少客户端-服务器连接数。这种做法有个问题是,那些小的 CSS 或 JS 自身也许可缓存,但如果它们内嵌在不可缓存的 HTML 中的话,当然也就不可缓存了。把很多小的 JS 脚本合并成一个大文件可能仍旧对 h2 有意义,因为这样可以更好地压缩处理并节省 CPU。


6.7.2 域名拆分


域名拆分(sharding)是为了利用浏览器针对每个域名开启多个连接的能力来并行下载资源。对于包含大量小型资源的网站,普遍的做法是拆分域名,以利用现代浏览器针能对每个域名开启 6 个连接的特性,充分利用可用带宽。


因为 HTTP/2 采取多路复用,所以域名拆分就不是必要的了,并且反而会让协议力图实现的目标落空。比较好的办法就是继续保持当前的域名拆分,但是确保这些域名共享同一张证书 [ 通配符 / 存储区域网络(SAN)],并保持服务器 IP 地址和端口相同,以便从浏览器网络归并(network coalescence)中收益,这样可以节省为单个域名连接建立的时间。


6.7.3 禁用cookie的域名


在 HTTP/1 下,请求和响应首部从不会被压缩。随着时间推移,首部大小已经增长了,超过单个 TCP 数据包的 cookie 可以说司空见惯。因此,在内容源和客户端之间来回传输首部信息的开销可能造成明显的延迟。


因此,对图片之类不依赖于 cookie 的资源,设置禁用 cookie 的域名是个合理的建议。


但是 HTTP/2 中,首部是被压缩的,并且客户端和服务器都会保留『首部历史』,避免重复传输已知信息。所以,如果你要重构站点,大可不必考虑禁用 cookie 的域名,这样能减少很多包袱。


静态资源也应该从同一域名提供;使用与主页面 HTTP 相同的域名,消除了额外的 DNS 查询以及(潜在的)socket 连接,它们都会减慢静态资源的获取。把阻塞渲染的资源放在同样的域名下,也可以提升性能。


6.7.4 资源预取


资源预取也是一项 Web 性能优化手段,它提示浏览器只要有可能就继续下载可缓存资源,并把这些资源缓存起来。尽管如此,如果浏览器很忙,或者资源下载花的时间太 长,预取请求将会被忽略。资源预取可以在 HTML 中插入 link 标签实现:


<link rel="prefetch" href="/important.css">
复制代码


也可以使用 HTTP 响应中的 Link 首部: Link: </important.css>; rel=prefetch


资源预取与 h2 引入的服务端推送并没多少关联。服务端推送用于让资源更快到达浏览器, 而资源预取相比推送的优点之一是,如果资源已经在缓存里,浏览器就不会浪费时间和带宽重复请求它。所以,可以把它看作 h2 推送的补充工具,而不是将被替代的特性。


6.8 现实情况中的性能


网络丢包是 h2 的命门,一次丢包机会就会让它的所有优化泡汤。


7. HTTP/2 实现


7.1 桌面Web浏览器


所有浏览器在进行 HTTP/2 传输时都需要使用 TLS(HTTPS),即使事实上HTTP/2 规范本身并没有强制要求 TLS。这个原因是:


  1. 从之前对 WebSocket 和 SPDY 的实验看来,使用 Upgrade 首部,通过 80 端口(明文的 HTTP 端口)通信时,通信链路上代理服务器的中断等因素会导致非常高的错误率。如果基于 443 端口(HTTPS 端口)上的 TLS 发起请求,错误率会显著降低,并且协议通信也更简洁。
  2. 人们越来越相信,考虑到安全和隐私,一切都应该被加密。HTTP/2 被视为一次推动全网加密通信发展的机会。


7.1.2 禁用HTTP/2


HTTP/2 毕竟是新鲜事物,现在很多浏览器都支持启用或禁用 h2。


7.1.3 支持 HTTP/2 服务端推送


服务端推送是 h2 中最令人兴奋也最难正确使用的特性之一,现在所有的主流浏览器都已经支持了此特性。


7.1.4 连接归并


如果需要建立一个新连接,而浏览器支持连接归并,那么通过复用之前已经存在的连接,就能够提升请求性能。这意味着可以跳过 TCP 和 TLS 的握手过程,改善首次请求新域名的性能。如果浏览器支持连接归并,它会在开启新连接之前先检查是否已经建立了到相同目的地的连接。


相同目的地具体指的是:已经存在连接,其证书对新域名有效,此域名可以被解析成那个连接对应的 IP 地址。如果上述条件都满足,那么浏览器会在已建立的连接上向该域名发起 HTTP/2 请求。


7.2 服务器、代理以及缓存


如果要通过 h2 传输内容,我们有几个选择。支持 HTTP/2 的网络设施大致有以下两类。

Web服务器 :通常所说的提供静态和动态内容服务的程序。


代理/缓存 :一般处在服务器和最终用户之间,可以提供缓存以减轻服务器负载,或进行额外加工。许多代理也能扮演 Web 服务器的角色。


在选择 HTTP/2 服务器时,我们需要检查、评估一些关键点。除了基本的通用性能、操作系统支持、学习曲线、可扩展性以及稳定性,还应当关注 Web 请求的依赖项优先级,以及对服务端推送的支持。


7.3 内容分发网络 CDN


内容分发网络(CDN)是反向代理服务器的全球性分布式网络,它部署在多个数据中心。CDN 的目标是通过缩短与最终用户的距离来减少请求往返次数,以此为最终用户提供高可用、高性能的内容服务。


大多数主流 CDN 是支持 HTTP/2 的,选择 CDN 时主要考虑的两点是:对服务端推送的支持,以及它们处理优先级的方式。这两点对现实世界中的 Web 性能影响重大。


8. HTTP/2调试


8.1 chrome devtools 可视化


Chrome 开发者工具中的 Network 栏,有助于简单直观地跟踪客户端和服务端的通讯,它 按下面表格的形式展示了若干信息:


  1. 资源名
  2. 资源大小
  3. 状态码
  4. 优先级
  5. 总加载时间
  6. 使用时间线方式分解加载时间


打开 devtools 的 Network 栏,鼠标放在瀑布流 Waterfall 的资源上,就会看到资源加载过程中各个阶段的详细时间


微信截图_20220427165520.png


  1. Connection Setup (连接设置)


  1. Queueing :请求被渲染引擎或者网络层延迟的时间,浏览器在以下情况下对请求排队
  • 存在更高优先级的请求。
  • 此源已打开六个 TCP 连接,达到限值。 仅适用于 HTTP/1.0 和 HTTP/1.1
  • 浏览器正在短暂分配磁盘缓存中的空间


  1. Connection Start (开始连接阶段)


  1. Stalled :请求可能会因 Queueing 中描述的任何原因而停止
  2. Proxy negotiation :浏览器与代理服务器协商请求花费的时间
  3. DNS Lookup :浏览器解析请求的 IP 地址花费的时间


  1. Request/Response (请求 / 响应)


  1. Request Sent :发送请求包含的数据花费的时间
  2. Waiting (TTFB) :等待初始响应花费的时间,也就是所说的首字节时间;这个数字包括等待服务器传输响应的时间,以及往返服务器的延迟
  3. Content Download :接收响应的数据所花费的时间


  1. Explanation (总时间)


  1. 其他


  1. ServiceWorker Preparation :浏览器正在启动 Service Worker
  2. Request to ServiceWorker :正在将请求发送到 Service Worker
  3. Receiving Push :浏览器正在通过 HTTP/2 服务器推送接收此响应的数据
  4. Reading Push :浏览器正在读取之前收到的本地数据


9. 展望未来


HTTP/2 的弱点之一就是依赖主流 TCP 实现。在 3.1.3 节中已经讨论过,TCP 连接受制于 TCP 慢启动、拥塞规避,以及不合理的丢包处理机制。用单个链接承载页面涉及的所有资源请求,就能享受多路复用带来的好处;然而面对 TCP 层级的队首阻塞时,我们还是束手无策。所以 Google 开发的 QUIC 采纳了 HTTP/2 的优点,并且避免了这些缺点。



推介阅读:

  1. HTTP2 详解 | Wangriyu’s Blog


相关文章
|
存储 安全 算法
Spring Security系列教程06--实现HTTP摘要认证
前言 在前面的2个章节中,一一哥 带大家认识了Spring Security中的第基本认证与表单认证 2种认证方式,其中表单认证是Spring Security默认的认证方式,也是开发时最常用的认证方式。有的小伙伴会问,不是还有第3种认证方式吗?对的,还有第三种摘要认证方式!在本文中,我们来学习了解一下HTTP摘要认证。 但是对于摘要认证,我们仅做了解即可,因为这种认证方式仅比基本认证稍微安全一点,开发时用的也不是很多。抱着艺多不压身的心态,我们多了解一点反正也没坏处。
524 0
|
网络协议 数据处理
【图解Http 学习摘要】一、http介绍、TCP/IP 协议族
【图解Http 学习摘要】一、http介绍、TCP/IP 协议族
【图解Http 学习摘要】一、http介绍、TCP/IP 协议族
|
安全 网络协议 网络安全
【图解Http 学习摘要】五、HTTPS 中的加密、证书介绍,不一直使用 HTTPS 的原因
【图解Http 学习摘要】五、HTTPS 中的加密、证书介绍,不一直使用 HTTPS 的原因
【图解Http 学习摘要】五、HTTPS 中的加密、证书介绍,不一直使用 HTTPS 的原因
|
网络协议 安全 网络安全
【图解Http 学习摘要】四、HTTP 缺点
【图解Http 学习摘要】四、HTTP 缺点
【图解Http 学习摘要】四、HTTP 缺点
|
网络协议 安全 网络安全
【图解Http 学习摘要】三、HTTP 协议基础、四次挥手
【图解Http 学习摘要】三、HTTP 协议基础、四次挥手
【图解Http 学习摘要】三、HTTP 协议基础、四次挥手
|
网络协议 大数据
【图解Http 学习摘要】二、IP,TCP 和 DNS、三次握手
【图解Http 学习摘要】二、IP,TCP 和 DNS、三次握手
【图解Http 学习摘要】二、IP,TCP 和 DNS、三次握手
|
1月前
|
前端开发
webpack如何设置devServer启动项目为https协议
webpack如何设置devServer启动项目为https协议
398 0
|
23天前
|
安全 前端开发 中间件
中间件中HTTP/HTTPS 协议
【6月更文挑战第3天】
18 3
|
1天前
|
安全 网络协议 网络安全
IP代理的三大协议:HTTP、HTTPS与SOCKS5的区别
**HTTP代理**适用于基本网页浏览,简单但不安全;**HTTPS代理**提供加密,适合保护隐私;**SOCKS5代理**灵活强大,支持TCP/UDP及认证,适用于绕过限制。选择代理协议应考虑安全、效率及匿名需求。
|
7天前
|
机器学习/深度学习 安全 数据安全/隐私保护
【计算机网络】深度学习HTTPS协议
【计算机网络】深度学习HTTPS协议
18 0