【Cause】你所应该知道的HTTP——优化篇

握手延迟

握手延迟是指开始 HTTP 通信之前所花费的时间,这里的影响因素是很多的。
握手延迟主要来自三方面:DNS 查询、TCP 三次握手和 TLS 四次握手。
DNS 查询也叫域名解析,是域名转换到IP地址的过程,现在基本上都是使用域名进行 URI 的表示,因此 DNS 查询是必须的步骤。
HTTP 是基于 TCP 的协议,因此 TCP 的三次握手也是不可避免的步骤。
TLS 四次握手的优化已经在 HTTPS 篇讲过,故此处不再重复。
我们优化的目标就是要尽量降低握手延迟,可用方案如下:

DNS 查询优化

  • 限制不同域名的数量:
    每一个域名,就意味着可能需要一次 DNS 查询,减少域名数量自然有助于减少查询次数。
  • 使用最近的DNS服务器:
    将物理距离的影响尽量降低,可以优化 DNS 服务的建设部署或购买靠谱的第三方服务。
  • 在主体页面 HTML 中使用 DNS 预取指令:
    DNS-Prefetching 是让具有此属性的域名不需要用户点击链接就在后台解析,待用户真的点击链接,就可以少去 DNS 查询的步骤,从而提高用户体验。

形如:

优化 TCP 连接

  • 借助 CDN 网络尽早响应:
    将物理距离的影响尽量降低,可以自行建设部署 CDN 网络或购买靠谱的第三方服务。
  • 使用 connection:keep-alive:
    这样能一定程度复用 TCP,减少 TCP 连接的建立。
  • 在主体页面 HTML 中使用 preconnect 指令:
    向浏览器提供提示,建议浏览器提前打开与链接网站的连接,以便在跟随链接时可以更快地获取链接内容。但不可滥用,同样会占用浏览器的连接数。

形如:

避免重定向

重定向是需要在建立完 TCP 连接后,服务器才以 301 或 302 的状态码告知客户端,这时候通常需要重新建立 TCP 连接。
因此最好的解决方案当然是彻底不要重定向。当然,非要重定向也可以考虑使用下面两个变通办法。

  • 利用 CDN 代替客户端在云端实现重定向;
  • 如果是同一个域名的重定向,使用 web 服务器的 rewrite 规则,避免浏览器跳转。

减少请求需要

这是最直接的解决办法,不需要那么多的请求需求,自然就没有延迟。主要思路不外乎合并外联资源,但这要适度,因为如果合并的文件过大,反而会降低了加载速度。

  • CSS Sprites(精灵图):
    即将多张图片合并成一张,原本需要多个请求获取的图片,现在只需要一次请求就能获取到。
  • 内联图片:
    使用 data:URL 模式,就是把图片编码为字串直接嵌入网页。
  • 合并或内联脚本和样式表:
    减少外联的 js 和 css 文件自然会减少请求,但内联有助于缓存,这需要平衡考虑。
  • 缓存:
    参见缓存篇提到的 Expires 或 Cache-Control。

队头阻塞

进阶篇中讲到 H1 的连接管理模型并未提供机制来同时请求多个资源。也就是说它需要发起请求、等待响应,之后才能发起下一个请求。资源将排队等待一问一答的加载,如果中间出现任何状况,都会导致剩下的工作被阻塞
我们优化的目标就是要尽量提高并发,减少队头阻塞的影响,可用方案如下:

域名拆分

现代浏览器为了解决这个问题会对单个域名开启 6 个左右的连接,通过各个连接分别发送请求。它实现了某种程度上的并行,但每个连接仍会受到“队头阻塞”的影响。
我们可以利用这一机制,将资源分布在多个域名下,这样一个域名6个请求,两个域名就能有 12 个请求。但这里也涉及到增加了 dns 查询、TCP 连接增加的问题,故需要达到一个最佳平衡,不可盲目。
Yahoo 研究表明,一个网站使用 2 个主机名进行资源加载可达到最优。

低效的TCP利用

TCP 的设计思路:对假设情况很保守,并能够公平对待同一网络的不同流量的应用。它的成功并不是因为传输速度快,而是因为它是最可靠的协议之一。
TCP 的通过慢启动,探索当前连接拥塞窗口的合适大小。即先发送少量数据包,如果接收到响应且无丢包,就在下一次发送多一倍的数据包,直到发包上限。也就是说说 TCP 的传输速度是逐步加快的,并不能一下子满速的。

拥塞窗口(congestion window)
拥塞窗口是指,在接收方确认数据包之前,发送方可以发出的 TCP 包的数量。
例如:如果拥塞窗口为 1,则发送方发出 1 个数据包之后,只有接收方确认了那个包,才能发送下一个。

【Cause】你所应该知道的HTTP——优化篇_第1张图片

而页面文件数据量本来就不大,建立 TCP 连接往往还没到最佳速度就结束了,即使多条连接并发也不能保证它们性能最优。
我们优化的目标就是要尽量复用 TCP 连接,可用方案如下:

使用长连接

使用 connection:keep-alive 是 H1 仅有的提高 TCP 使用率的办法。

臃肿的消息首部

H1 虽然提供了压缩请求内容(body)的机制,但是消息首部却无法压缩。特别是其中的 cookie 有时很大,这样就自然增加了每次重复的数据量传输,而且自定义头部的增加,这种情况越来越严重。
我们优化的目标就是要尽量减少消息首部,可用方案如下:

减少cookie

cookie 虽然保存在本地,但每次请求都会被发送到服务器,需要尽量减小cookie 大小。需要较大的信息存储时,可以考虑使用其他客户端的缓存,比如:WebStorage、WebDatabases 等。

分离资源域名与ajax域名

资源传输一般都不需要 cookie,故可以在这类域名上设置 cookie 禁用。

受限的优先级设置

H1 基本没有关于优先级的设计,单纯由浏览器决定,浏览器的有些解析过程还会阻塞资源的请求。
我们优化的目标就是使用浏览器的特性手动安排优先级,可用方案如下:

合理安排资源加载

  • JS 放 HTML 文档末尾可以防止阻塞其他资源加载;
  • 如果 JS 执行顺序无关紧要,并且必须在 onload 事件触发之前执行,可以设置 asyn 属性;
  • 如果 JS 执行顺序很重要,并且允许脚本在 dom 加载完后执行,可以设置 defer 属性;
  • 对不会影响页面初次渲染的 JS 脚本,可以在 onload 事件之后再通过动态新建标签请求加载。

升级到HTTP/2.0

升级到 H2 可以解决大部分上面提到的有关 H1 的性能问题,上面提到的“队头阻塞”和“低效的 TCP 利用”会被 H2 的多路复用解决,“臃肿的消息首部”会被首部表和首部压缩解决,“受限的优先级设置”会被请求优先级解决。
那前面提到的一些优化方案是否还需要保留呢?答案是否定的,一些优化方案不单没有效果反而会成为反模式,这里需要注意:

反模式:生成精灵图和资源合并/内联

单个文件都是可以被缓存的,合并/内联实际上会失去缓存的特性。在 H1 的是时代牺牲缓存减少请求数是划算的,但 H2 时代所有资源都可并发,并且只有一个连接,所以缓存的优势会更大。

反模式:域名拆分

为了增加并发请求数,H1 时代会将资源分散到多个域名下,但 H2 时代只有一个连接,并且都可并行请求,所以多个域名只会增加 DNS 解析的代价和建立连接的耗时。

反模式:禁用 cookie 的域名

H1 时代一些不需要 cookie 的资源可以放在禁用 cookie 的域名下减少请求大小,但 H2 时代的头部是压缩处理的,所以将资源的域名都与主页面一致反而可以减少 DNS 解析。

你可能感兴趣的:(http,前端)