最近粗线了很多 HTTP2 相关的帖子和讨论,感受新一轮的潮流在造成,因此最近找了本 HTTP2 相关书籍作知识储备,恰好记成笔记以备后询 ~css
若是但愿获取本书的 PDF 资源,能够关注文末二维码加微信群找群主要~html
这本书自己不错,缺点就是翻译的有点蹩脚,另外由于是 2017 年出的书,因此有些内容时效性不太好,好比关于 Chrome 的部分,因此我根据 Chrome 的官方文档增长了点内容 😅前端
OPTIONS
方法、Upgrade
首部、Range
请求、压缩和传输编码、管道化等功能。由于强制要求客户端提供 Host 首部,因此虚拟主机托管成为可能,也就是在一个 IP 上提供多个 Web 服务。另外使用了 keep-alive
以后,Web 服务器也不须要在每一个响应以后关闭链接。这对于提高性能和效率而言意义重大,由于浏览器不再用为每一个请求从新发起 TCP 链接了。HTTP2 被但愿达到如下特性:web
不少网站已经在用HTTP/2(h2)了,好比 Facebook、Instagram、Twitter 等,下面介绍如下如何本身搭建 h2 服务器。要运行 h2 服务器,主要分两步:ajax
证书能够经过三种方式获取:算法
前两个方法 将建立自签名证书,仅用于测试,因为不是 CA 签发的,浏览器会报警chrome
后面关于建立 h2 服务器的步骤就不记了,能够百度下api
从用户在浏览器中点击连接到页面呈如今屏幕上,在此期间到底发生了什么?浏览器请求 Web 页面时,会执行重复流程,获取在屏幕上绘制页面须要的全部信息。为了更容易理解,咱们把这一过程分红两部分:资源获取、页面解析/渲染。promise
资源请求流程图:浏览器
流程为:
资源响应流程图:
页面上的每一次点击,都须要重复执行前面那些流程,给网络带宽和设备资源带来压力。Web 性能优化的的核心,就是加快甚至干脆去掉其中的某些步骤。
下面网络级别的性能指标,它会影响整个 Web 页面加载。
HTTP/1 的问题天然是 HTTP/2 要解决的核心问题
浏览器不多只从一个域名获取一份资源,通常但愿能同时获取许多资源。h1 有个特性叫管道化(pipelining),容许一次发送一组请求,可是只能按照发送顺序依次接收响应。管道化备受互操做性和部署的各类问题的困扰,基本没有实用价值。 在请求应答过程当中,若是出现任何情况,剩下全部的工做都会被阻塞在那次请求应答以后。这就是『队头阻塞』,它会阻碍网络传输和 Web 页面渲染,直至失去响应。为了防止这种问题,现代浏览器会针对单个域名开启 6 个链接,经过各个链接分别发送请求。它实现了某种程度上的并行,可是每一个链接仍会受到的影响。
传输控制协议(TCP)的设计思路是:对假设状况很保守,并可以公平对待同一网络的不一样流量的应用。涉及的核心概念就是拥塞窗口(congestion window)。『拥塞窗口』是指,在接收方确认数据包以前,发送方能够发出的 TCP 包的数量。 例如,若是拥塞窗口指定为 1,那么发送方发出 1 个数据包以后,只有接收方确认了那个包,才能发送下一个。
TCP 有个概念叫慢启动(Slow Start),它用来探索当前链接对应拥塞窗口的合适大小。慢启动的设计目标是为了让新链接搞清楚当前网络情况,避免给已经拥堵的网络继续添乱。它容许发送者在收到每一个确认回复后额外发送 1 个未确认包。这意味着新链接在收到 1 个确认回复以后,能够发送 2 个数据包;在收到 2 个确认回复以后,能够发 4 个,以此类推。这种几何级数增加很快到达协议规定的发包数上限,这时候链接将进入拥塞避免阶段。
这种机制须要几回往返数据请求才能得知最佳拥塞窗口大小。但在解决性能问题时,就这区区几回数据往返也是很是宝贵的时间成本。若是你把一个数据包设置为最大值下限 1460 字节,那么只能先发送 5840 字节(假定拥塞窗口为 4),而后就须要等待接收确认回复。理想状况下,这须要大约 9 次往返请求来传输完整个页面。除此以外,浏览器通常会针对同一个域名开启 6 个并发链接,每一个链接都免不了拥塞窗口调节。
传统 TCP 实现利用拥塞控制算法会根据数据包的丢失来反馈调整。若是数据包确认丢失了,算法就会缩小拥塞窗口。这就相似于咱们在黑暗的房间摸索,若是腿碰到了桌子就会立刻换个方向。若是遇到超时,也就是等待的回复没有按时抵达,它甚至会完全重置拥塞窗口并从新进入慢启动阶段。新的算法会把其余因素也考虑进来,例如延迟,以提供更妥善的反馈机制。
前面提到过,由于 h1 并不支持多路复用,因此浏览器通常会针对指定域名开启 6 个并发链接。这意味着拥塞窗口波动也会并行发生 6 次。TCP 协议保证那些链接都能正常工做,可是不能保证它们的性能是最优的。
虽然 h1 提供了压缩被请求内容的机制,可是消息首部却没法压缩。消息首部可不能忽略,尽管它比响应资源小不少,但它可能占据请求的绝大部分(有时候多是所有)。若是算上 cookie,就更大了。
消息首部压缩的缺失也容易致使客户端到达带宽上限,对于低带宽或高拥堵的链路尤为如此。『体育馆效应』(Stadium Effect)就是一个经典例子。若是成千上万人同一时间出如今同一地点(例如重大致育赛事),会迅速耗尽无线蜂窝网络带宽。这时候,若是能压缩请求首部,把请求变得更小,就可以缓解带宽压力,下降系统的总负载。
若是浏览器针对指定域名开启了多个 socket(每一个都会受队头阻塞问题的困扰),开始请求资源,这时候浏览器能指定优先级的方式是有限的:要么发起请求,要么不发起。然而 Web 页面上某些资源会比另外一些更重要,这必然会加剧资源的排队效应。这是由于浏览器为了先请求优先级高的资源,会推迟请求其余资源。可是优先级高的资源获取以后,在处理的过程当中,浏览器并不会发起新的资源请求,因此服务器没法利用这段时间发送优先级低的资源,总的页面下载时间所以延长了。还会出现这样的状况:一个高优先级资源被浏览器发现,可是受制于浏览器处理的方式,它被排在了一个正在获取的低优先级资源以后。
现在 Web 页面上请求的不少资源彻底独立于站点服务器的控制,咱们称这些为第三方资源。现代 Web 页面加载时长中每每有一半消耗在第三方资源上。虽然有不少技巧能把第三方资源对页面性能的影响降到最低,可是不少第三方资源都不在 Web 开发者的控制范围内,因此极可能其中有些资源的性能不好,会延迟甚至阻塞页面渲染。使人扫兴的是, h2 对此也一筹莫展。
2010 年,谷歌把 Web 性能做为影响页面搜索评分的重要因素之一,性能指标开始在搜索引擎中发挥做用。对于不少 Web 页面,浏览器的大块时间并非用于呈现来自网站的主体内容(一般是 HTML),而是在请求全部资源并渲染页面。
所以,Web 开发者逐渐更多地关注经过减小客户端网络延迟和优化页面渲染性能来提高Web 性能。
在与服务主机创建链接以前,须要先解析域名;那么,解析越快就越好。下面有一些方法:
<link rel="dns-prefetch" href="//ajax.googleapis.com">
复制代码
本章前面提到过,开启新链接是一个耗时的过程。若是链接使用 TLS(也确实应该这么作),开销会更高。下降这种开销的方法以下
preconnect
指令,链接在使用以前就已经创建好了,这样处理流程的关键路径上就没必要考虑链接时间了,preconnect
不光会解析 DNS,还会创建 TCP 握手链接和 TLS 协议(若是须要)<link rel="preconnect" href="//fonts.example.com" crossorigin>
复制代码
若是要从同一个域名请求大量资源,浏览器将自动开启到服务器的并发链接,避免资源获取瓶颈。虽然如今大部分浏览器支持 6 个或更多的并发链接数目,但你不能直接控制浏览器针对同一域名的并发链接数。
重定向一般触发与额外域名创建链接。在移动网络中,一次额外的重定向可能把延迟增长数百毫秒,这不利于用户体验,并最终会影响到网站上的业务。简单的解决方案就是完全消灭重定向,由于对于重定向的使每每并无合理缘由。若是它们不能被直接消灭,你还有两个选择:
没有什么比直接从本地缓存获取资源来得更快,由于它根本就不须要创建网络链接。
能够经过 HTTP 首部指定 cache control 以及键 max-age
(以秒为单位),或者 expires
首部。
我的信息(用户偏好、财务数据等)绝对不能在网络边缘缓存,由于它们不能共享。时间敏感的资源也不该该缓存,例如实时交易系统上的股票报价。这就是说,除此以外其余一切都是能够缓存的,即便仅仅缓存几秒或几分钟。对于那些不是常常更新,然而一旦有变化就必须马上更新的资源,例如重大新闻,能够利用各大 CDN 厂商提供的缓存清理(purging)机制处理。这种模式被称为『一直保留,直到被通知』(Hold til Told),意思是永久缓存这些资源,等收到通知后才删除。
若是缓存 TTL 过时,客户端会向服务器发起请求。在多数状况下,收到的响应其实和缓存的版本是同样的,从新下载已经在缓存里的内容也是一种浪费。HTTP 提供条件请求机制,客户端能以有效方式询问服务器:『若是内容变了,请返回内容自己;不然,直接告诉我内容没变。』当资源不常常变化时,使用条件请求能够显著节省带宽和性能;可是,保证资源的最新版迅速可用也是很是重要的。使用条件缓存能够经过如下方法。
Last-Modified-Since
。仅当最新内容在首部中指定的日期以后被更新过,服务器才返回完整内容;不然只返回 304 响应码,并在响应首部中附带上新的时间戳 Date
字段。ETag
;它惟一标识所请求的资源。ETag 由服务器 提供,内嵌于资源的响应首部中。服务器会比较当前 ETag
与请求首部中收到的 ETag
,若是一致,就只返回 304 响应码;不然返回完整内容。通常来讲,大多数 Web 服务器会对图片和 CSS/JS 使用这些技术,但你也能够将其用到其余资源。
全部的文本内容(HTML、JS、CSS、SVG、XML、JSON、字体等),均可以压缩和极简化。这两种方法组合起来,能够显著减小资源大小。更少字节数对应着更少的请求与应答,也就意味着更短的请求时间。
极简化(minification, 混淆)是指从文本资源中剥离全部非核心内容的过程。一般,要考虑方便人类阅读和维护,而浏览器并不关心可读性,放弃代码可读性反而能节省空间。在极简化的基础上,压缩能够进一步减小字节数。它经过可无损还原的算法减小资源大小。在发送资源以前,若是服务器进行压缩处理,能够节省 90% 的大小。
在屏幕上绘制第一个像素以前,浏览器必须确保 CSS 已经下载完整。尽管浏览器的预处理器很智能,会尽早请求整个页面所须要的 CSS,可是把 CSS 资源请求放在页面靠前仍然是种最佳实践,具体位置是在文档的 head 标签里,并且要在任何 JS 或图片被请求和处理以前。
默认状况下,若是在 HTML 中定位了 JS,它就会被请求、解析,而后执行。在浏览器处理完这个 JS 以前,会阻止其后任何资源的下载渲染。然而大多数时候,这种默认的阻塞行为致使了没必要要的延迟,甚至会形成单点故障。为了减轻 JS 阻塞带来的潜在影响,下面针对己方资源(你能控制的)和第三方资源(你不能控制的)推荐了不一样的策略
onload
事件触发以前运行,那么能够设置 async
属性,像这样:<script async src="/js/myfile.js"> 复制代码
只需作到下载 JS 与解析 HTML 并行,就能极大地提高总体用户体验。慎用 document.write
指令,由于极可能中断页面执行,因此须要仔细测试。<script defer src="/js/myjs.js"> 复制代码
onload
事件,能够考虑经过 iframe
获取 JS,由于它的处理独立于主页面。可是,经过 iframe
下载的 JS 访问不了主页面上的元素。对大多数网站而言,图片的重要性和比重在不断增长。既然图片主导了多数现代网站,优化它们就可以得到最大的性能回报。全部图片优化手段的目标都是在达到指定视觉质量的前提下传输最少的字节。
HTTP/2 对每一个域名只会开启一个链接,因此 HTTP/1.1 下的一些诀窍对它来讲只会拔苗助长。
详细看 6.7 节
HTTP/1.1 孕育了各类性能优化手段与诀窍,能够帮助咱们深刻理解 Web 及其内部实现。HTTP/2 的目标之一就是淘汰掉众多(并非所有)此类诀窍。
在升级到 HTTP/2 以前,你应该考虑:
任何不支持 h2 的客户端都将简单地退回到 h1,并仍然能够访问你的站点基础设施。
全部主流浏览器只能访问基于 TLS(即 HTTPS 请求)的 h2。
Web 开发者以前花费了大量心血来充分使用 h1,而且已经总结了一些诀窍,例如资源合并、域名拆分、极简化、禁用 cookie 的域名、生成精灵图,等等。因此,当得知这些实践中有些在 h2 下变成反模式时,你可能会感到吃惊。例如,资源合并(把不少 CSS/JS 文件拼合成一个)能避免浏览器发出多个请求。对 h1 而言这很重要,由于发起请求的代价很高;可是在 h2 的世界里,这部分已经作了深度优化。放弃资源合并的结果多是,针对单个资源发起请求的代价很低,但浏览器端能够进行更细粒度的缓存。
详细看 6.7 节
本章将全面探讨 HTTP/2 的底层工做原理,深刻到数据层传输的帧及其通讯方式。
HTTP/2 大体能够分为两部分
h2 有些特色须要关注一下:
与彻底无状态的 h1 不一样的是,h2 把它所承载的帧(frame)和流(stream)共同依赖的链接层元素捆绑在一块儿,其中既包含链接层设置也包含首部表。也就是说,与以前的 HTTP 版本不一样,每一个 h2 链接都有必定的开销。之因此这么设计,是考虑到收益远远超过其开销。
在链接的时候,HTTP/2 提供两种协议发现的机制:
在协议制定过程当中,很早就把小数点去掉了,这代表将来的 HTTP 版本不能保证语义的向后兼容,也就是说只有 HTTP/2 没有 HTTP/2.0、HTTP/2.2
HTTP/2 是基于帧(frame)的协议,采用分帧是为了将重要信息都封装起来,让协议的解析方能够轻松阅读、解析并还原信息。 相比之下,h1 不是基于帧的,而是以文本分隔。因此解析 h1 的请求或响应可能会出现如下问题:
从另外一方面来讲,有了帧,处理协议的程序就能预先知道会收到什么。下图是一个 HTTP/2 帧的结构
前 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 内置了名为扩展帧的处理新的帧类型的能力。依靠这种机制,客户端和服务器的实现者能够实验新的帧类型,而无需制定新协议。按照规范,任何客户端不能识别的帧都会被丢弃,因此网络上新出现的帧就不会影响核心协议。固然,若是你的应用程序依赖于新的帧,而中间代理会丢弃它,那么可能会出现问题。
HTTP/2 规范中的流(stream):HTTP/2 链接上独立的、双向的帧序列交换。你能够将流看做在链接上的一系列帧,用来传输一对请求/响应消息。若是客户端想要发出请求,它会开启一个新的流,服务器也在这个流上回复。这与 h1 的请求、响应流程相似,区别在于,由于有分帧,因此多个请求和响应能够交错,而不会互相阻塞。流 ID(帧首部的第 6~9 字节)用来标识帧所属的流。
客户端到服务器的 h2 链接创建以后,经过发送 HEADERS
帧来启动新的流,若是首部须要跨多个帧,可能还发会送 CONTINUATION
帧。
HTTP 消息泛指 HTTP 请求或响应。一个消息至少由 HEADERS 帧(用来初始化流)组成,而且能够另外包含 CONTINUATION
和 DATA
帧,以及其余的 HEADERS
帧。 下图是普通 GET 请求的示例流程
POST 和 GET 的主要差异之一就是 POST 请求一般包含客户端发出的大量数据。下图是 POST 消息对应的各帧可能的样子
h1 的请求和响应都分红消息首部和消息体两部分;与之相似,h2 的请求和响应分红 HEADERS
帧和 DATA
帧。HTTP/1 和 HTTP/2 消息的下列差异是须要注意
h2 的新特性之一是基于流的流量控制。h1 中只要客户端能够处理,服务端就会尽量快地发送数据,h2 提供了客户端调整传输速度的能力,服务端也一样能够调整传输的速度。
WINDOW_UPDATE
帧用于执行流量控制功能,能够做用在单独某个流上(指定具体 Stream Identifier
)也能够做用整个链接 (Stream Identifier
为 0x0),只有 DATA
帧受流量控制影响。初始化流量窗口后,发送多少负载,流量窗口就减小多少,若是流量窗口不足就没法发送,WINDOW_UPDATE
帧能够增长流量窗口大小。流创建的时候,窗口大小默认 65535(2^16-1)字节。
流的最后一个重要特性是依赖关系。
现代浏览器会尽可能以最优的顺序获取资源,由此来优化页面性能。在没有多路复用的时候,在它能够发出对新对象的请求以前,须要等待前一个响应完成。有了 h2 多路复用的能力,客户端就能够一次发出全部资源的请求,服务端也能够当即着手处理这些请求。由此带来的问题是,浏览器失去了在 h1 时代默认的资源请求优先级策略。假设服务器同时接收到了 100 个请求,也没有标识哪一个更重要,那么它将几乎同时发送每一个资源,次要元素就会影响到关键元素的传输。
h2 经过流的依赖关系来解决上面这个问题。经过 HEADERS
帧和 PRIORITY
帧,客户端能够明确地和服务端沟通它须要什么,以及它须要这些资源的顺序。这是经过声明依赖关系树和树里的相对权重实现的。
流能够被标记为依赖其余流,所依赖的流完成后再处理当前流。每一个依赖 (dependency) 后都跟着一个权重 (weight),这一数字是用来肯定依赖于相同的流的可分配可用资源的相对比例。
其余时候也能够经过 PRIORITY
帧调整流优先级。
设置优先级的目的是为了让端点表达它所指望对端在并发的多个流之间如何分配资源的行为。更重要的是,当发送容量有限时,能够使用优先级来选择用于发送帧的流。
升单个对象性能的最佳方式,就是在它被用到以前就放到浏览器的缓存里面。这正是 h2 服务端推送的目的。
若是服务器决定要推送一个对象(RFC 中称为『推送响应』),会构造一个 PUSH_PROMISE
帧。这个帧有不少重要属性:
PUSH_PROMISE
帧首部中的流 ID (Promised Stream ID)用来响应相关联的请求。推送的响应必定会对应到客户端已发送的某个请求。若是浏览器请求一个主体 HTML 页面,若是要推送此页面使用的某个 JavaScript 对象,服务器将使用请求对应的流 ID 构造 PUSH_PROMISE
帧。PUSH_PROMISE
帧的首部块与客户端请求推送对象时发送的首部块是类似的。因此客户端有办法放心检查将要发送的请求。:method
首部的值必须确保安全。安全的方法就是幂等的那些方法,这是一种不改变任何状态的好办法。例如,GET 请求被认为是幂等的,由于它一般只是获取对象,而 POST 请求被认为是非幂等的,由于它可能会改变服务器端的状态。PUSH_PROMISE
帧应该更早发送,应当早于客户端接收到可能承载着推送对象的 DATA
帧。假设服务器要在发送 PUSH_PROMISE
以前发送完整的 HTML,那客户端可能在接收到 PUSH_PROMISE
以前已经发出了对这个资源的请求。h2 足够健壮,能够优雅地解决这类问题,但仍是会有些浪费。PUSH_PROMISE
帧会指示将要发送的响应所使用的流 ID若是客户端对 PUSH_PROMISE
的任何元素不满意,就能够按照拒收缘由选择重置这个流(使用 RST_STREAM
),或者发送 PROTOCOL_ERROR
(在 GOAWAY
帧中)。常见的状况是缓存中已经有了这个对象。
假设客户端不拒收推送,服务端会继续进行推送流程,用 PUSH_PROMISE
中指明 ID 对应的流来发送对象
若是服务器接收到一个页面的请求,它须要决定是推送页面上的资源仍是等客户端来请求。决策的过程须要考虑到以下方面
若是服务器选择正确,那就真的有助于提高页面的总体性能,反之则会损耗页面性能。
现代网页平均有不少请求,这些请求之间几乎没有新的的内容,这是极大的浪费。
首部列表 (Header List) 是零个或多个首部字段 (Header Field) 的集合。当经过链接传送时,首部列表经过压缩算法(即下文 HPACK) 序列化成首部块 (Header Block),不用 GZIP 是由于它有泄漏加密信息的风险。HPACK 是种表查找压缩方案,它利用霍夫曼编码得到接近 GZIP 的压缩率。
而后,序列化的首部块又被划分红一个或多个叫作首部块片断 (Header Block Fragment) 的字节序列,并经过 HEADERS
、PUSH_PROMISE
,或者 CONTINUATION
帧进行有效负载传送。
假设客户端按顺序发送以下请求首部:
Header1: foo
Header2: bar
Header3: bat
复制代码
当客户端发送请求时,能够在首部数据块中指示特定首部及其应该被索引的值。它会建立一张表:
索引 | 首部名称 | 值 |
---|---|---|
62 | Header1 | foo |
63 | Header2 | bar |
64 | Header3 | bat |
若是服务端读到了这些请求首部,它会照样建立一张表。客户端发送下一个请求的时候, 若是首部相同,它能够直接发送:62 63 64
,服务器会查找先前的表格,并把这些数字还原成索引对应的完整首部。首部压缩机制中每一个链接都维护了本身的状态。
HPACK 的实现比这个要复杂得多,好比:
:method: GET
在静态表中索引为 2。按规定,静态表包含 61 个条目,因此上例索引编号从 62 开始。:path: /foo.html
,其值每次都不一样)线上传输的 h2 信息是通过压缩的二进制数据。
下面是一个简单的 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 首部)
HTTP/2 大部分状况下传输 web 页面比 HTTP/1.1 快。
网络条件相同,使用不一样浏览器客户端,一样的网站页面加载性能可能差异很大。
延迟是指数据包从一个端点到另外一个端点所花的时间。有时,它也表示数据包到达接收方而后返回发送方所需的时间,又称为往返时延(RTT),长度通常以毫秒计。
影响延迟的因素众多,但有两个是最重要的:端点间的距离,以及所用传输介质
两点之间的网线不会是笔直的,另外各类网关、路由器、交换机以及移动基站等(也包括服务器应用自己)都会增长延迟
若是网络中传输的数据包没有成功到达目的地,就会发生丢包,这一般是由网络拥堵形成的。
频繁丢包会影响 h2 的页面传输,主要是由于 h2 开启单一 TCP 链接,每次有丢包/拥堵时,TCP 协议就会缩减 TCP 窗口。
若是 TCP 流上丢了一个数据包,那么整个 h2 链接都会停顿下来,直到该数据包重发并被接收到。
服务端推送让服务器具有了在客户端请求以前就推送资源的能力。 测试代表,若是合理使用推送,页面渲染时间能够减小 20%~50%。
然而,推送也会浪费带宽,这是由于服务端可能试图推送那些在客户端已经缓存的资源,致使客户端收到并不须要的数据。客户端确实能够发送 RST_STREAM 帧来拒绝服务器的 PUSH_PROMISE
帧,可是 RST_STREAM
并不会即刻到达,因此服务器仍是会发送一些多余的信息。
若是用户第一次访问页面时,就能向客户端推送页面渲染所需的关键 CSS 和 JS 资源,那么服务端推送的真正价值就实现了。不过,这要求服务器端实现足够智能,以免『推送承诺』(push promise)与主体 HTML 页面传输竞争带宽。
理想状况下,服务端正在处理 HTML 页面主体请求时才会发起推送。有时候,服务端须要作一些后台工做来生成 HTML 页面。这时候服务端在忙,客户端却在等待,这正是开始向客户端推送所需资源的绝佳时机。
首字节时间(TTFB)用于测量服务器的响应能力。是从客户端发起 HTTP 请求到客户端浏览器收到资源的第一个字节所经历的时间。由 socket 链接时间、发送 HTTP 请求所需时间、收到页面第一个字节所需时间组成。
h1 中,客户端针对单个域名在每一个链接上依次请求资源,并且服务器会按序发送这些资源。客户端只有接收了以前请求的资源,才会再请求剩下的资源,服务器接着继续响应新的资源请求。这个过程会一直重复,直到客户端接收完渲染页面所需的所有资源。
与 h1 不一样,经过 h2 的多路复用,客户端一旦加载了 HTML,就会向服务器并行发送大量请求。相比 h1,这些请求得到响应的时间之和通常会更短;可是由于是请求是同时发出的,而单个请求的计时起点更早,因此 h2 统计到的 TTFB 值会更高。
HTTP/2 比 h1 确实作了更多的工做,其目的就是为了从整体上提高性能。下面是一些 h1 没有,但 h2 实现了的
下图是使用 h1 和 h2 加载同一个页面的加载时序对比,整体来讲 h2 体验更好
许多网站会使用各类统计、跟踪、社交以及广告平台,就会引入各类第三方的资源。
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 中使用 |
精灵图(spriting)是指把不少小图片拼合成一张大图,这样只需发起一个请求就能够覆盖多个图片元素。在 HTTP/2 中,针对特定资源的请求再也不是阻塞式的,不少请求能够并行处理;就性能而言,生成精灵图已失去意义,由于多路复用和首部压缩去掉了大量的请求开销。
与之相似,小的文本资源,例如 JS 和 CSS,会依照惯例合并成一份更大的资源,或者直接内嵌在主体 HTML 中,这也是为了减小客户端-服务器链接数。这种作法有个问题是,那些小的 CSS 或 JS 自身也许可缓存,但若是它们内嵌在不可缓存的 HTML 中的话,固然也就不可缓存了。把不少小的 JS 脚本合并成一个大文件可能仍旧对 h2 有意义,由于这样能够更好地压缩处理并节省 CPU。
域名拆分(sharding)是为了利用浏览器针对每一个域名开启多个链接的能力来并行下载资源。对于包含大量小型资源的网站,广泛的作法是拆分域名,以利用现代浏览器针能对每一个域名开启 6 个链接的特性,充分利用可用带宽。
由于 HTTP/2 采起多路复用,因此域名拆分就不是必要的了,而且反而会让协议力图实现的目标落空。比较好的办法就是继续保持当前的域名拆分,可是确保这些域名共享同一张证书 [ 通配符 / 存储区域网络(SAN)],并保持服务器 IP 地址和端口相同,以便从浏览器网络归并(network coalescence)中收益,这样能够节省为单个域名链接创建的时间。
在 HTTP/1 下,请求和响应首部从不会被压缩。随着时间推移,首部大小已经增加了,超过单个 TCP 数据包的 cookie
能够说司空见惯。所以,在内容源和客户端之间来回传输首部信息的开销可能形成明显的延迟。
所以,对图片之类不依赖于 cookie
的资源,设置禁用 cookie
的域名是个合理的建议。
可是 HTTP/2 中,首部是被压缩的,而且客户端和服务器都会保留『首部历史』,避免重复传输已知信息。因此,若是你要重构站点,大可没必要考虑禁用 cookie
的域名,这样能减小不少包袱。
静态资源也应该从同一域名提供;使用与主页面 HTTP 相同的域名,消除了额外的 DNS 查询以及(潜在的)socket 链接,它们都会减慢静态资源的获取。把阻塞渲染的资源放在一样的域名下,也能够提高性能。
资源预取也是一项 Web 性能优化手段,它提示浏览器只要有可能就继续下载可缓存资源,并把这些资源缓存起来。尽管如此,若是浏览器很忙,或者资源下载花的时间太 长,预取请求将会被忽略。资源预取能够在 HTML 中插入 link 标签实现:
<link rel="prefetch" href="/important.css">
复制代码
也能够使用 HTTP 响应中的 Link
首部: Link: </important.css>; rel=prefetch
资源预取与 h2 引入的服务端推送并没多少关联。服务端推送用于让资源更快到达浏览器, 而资源预取相比推送的优势之一是,若是资源已经在缓存里,浏览器就不会浪费时间和带宽重复请求它。因此,能够把它看做 h2 推送的补充工具,而不是将被替代的特性。
网络丢包是 h2 的命门,一次丢包机会就会让它的全部优化泡汤。
全部浏览器在进行 HTTP/2 传输时都须要使用 TLS(HTTPS),即便事实上HTTP/2 规范自己并无强制要求 TLS。这个缘由是:
Upgrade
首部,经过 80 端口(明文的 HTTP 端口)通讯时,通讯链路上代理服务器的中断等因素会致使很是高的错误率。若是基于 443 端口(HTTPS 端口)上的 TLS 发起请求,错误率会显著下降,而且协议通讯也更简洁。HTTP/2 毕竟是新鲜事物,如今不少浏览器都支持启用或禁用 h2。
服务端推送是 h2 中最使人兴奋也最难正确使用的特性之一,如今全部的主流浏览器都已经支持了此特性。
若是须要创建一个新链接,而浏览器支持链接归并,那么经过复用以前已经存在的链接,就可以提高请求性能。这意味着能够跳过 TCP 和 TLS 的握手过程,改善首次请求新域名的性能。若是浏览器支持链接归并,它会在开启新链接以前先检查是否已经创建了到相同目的地的链接。
相同目的地具体指的是:已经存在链接,其证书对新域名有效,此域名能够被解析成那个链接对应的 IP 地址。若是上述条件都知足,那么浏览器会在已创建的链接上向该域名发起 HTTP/2 请求。
若是要经过 h2 传输内容,咱们有几个选择。支持 HTTP/2 的网络设施大体有如下两类。
Web服务器 :一般所说的提供静态和动态内容服务的程序。
代理/缓存 :通常处在服务器和最终用户之间,能够提供缓存以减轻服务器负载,或进行额外加工。许多代理也能扮演 Web 服务器的角色。
在选择 HTTP/2 服务器时,咱们须要检查、评估一些关键点。除了基本的通用性能、操做系统支持、学习曲线、可扩展性以及稳定性,还应当关注 Web 请求的依赖项和优先级,以及对服务端推送的支持。
内容分发网络(CDN)是反向代理服务器的全球性分布式网络,它部署在多个数据中心。CDN 的目标是经过缩短与最终用户的距离来减小请求往返次数,以此为最终用户提供高可用、高性能的内容服务。
大多数主流 CDN 是支持 HTTP/2 的,选择 CDN 时主要考虑的两点是:对服务端推送的支持,以及它们处理优先级的方式。这两点对现实世界中的 Web 性能影响重大。
Chrome 开发者工具中的 Network 栏,有助于简单直观地跟踪客户端和服务端的通信,它 按下面表格的形式展现了若干信息:
打开 devtools 的 Network 栏,鼠标放在瀑布流 Waterfall 的资源上,就会看到资源加载过程当中各个阶段的详细时间
HTTP/2 的弱点之一就是依赖主流 TCP 实现。在 3.1.3 节中已经讨论过,TCP 链接受制于 TCP 慢启动、拥塞规避,以及不合理的丢包处理机制。用单个连接承载页面涉及的全部资源请求,就能享受多路复用带来的好处;然而面对 TCP 层级的队首阻塞时,咱们仍是一筹莫展。因此 Google 开发的 QUIC 采纳了 HTTP/2 的优势,而且避免了这些缺点。
推介阅读:
PS:欢迎你们关注个人公众号【前端下午茶】,一块儿加油吧~
另外能够加入「前端下午茶交流群」微信群,长按识别下面二维码便可加我好友,备注加群,我拉你入群~