如何提高网页加载性能

摘自 https://github.com/xitu/gold-miner/blob/master/TODO/building-a-shop-with-sub-second-page-loads-lessons-learned.mdphp


全方位提高网站打开速度:前端、后端、新的技术

这里是 咱们 充分利用对于网络缓存和 NoSQL 系统的研究,作出一个能够容纳几十万经过电视宣传慕名而来的访问者的 网上商城 的故事,以及咱们从中学到的一切。css

"Shark Tank"(美国),"Dragons’ Den"(英国)或" Die Höhle der Löwen(DHDL)"(德国)等电视节目为年轻初创公司供了一次在众多观众前向商业大亨推销本身产品的机会。然而,主要的好处每每不在于评审团提供的战略投资——只有少数交易会完成——而是在电视节目播放期间引起的关注:即便是几分钟的直播也能给网站带来几十万的新用户,同时可以提升几周、几个月甚至永久性的网站基本活跃水平。也就是说,若是网站能够抓住初始负载尖峰,而且不拒绝用户请求……html

仅仅可用是不够的——延迟是关键!

网上商城的盈利压力特别大,由于他们不仅是消遣项目(诸如博客),但一般因为创始人自己有大量投资支持,必须转化为利润。很明显,对于商业业务来讲,最坏的状况是网站过载,在此期间服务器不得不丢掉部分用户请求甚至可能彻底崩溃。这并不像你想象的那样罕见:在 DHDL 的这一季,大约有一半的网上商店在直播现场就没法链接了。而且,保持在线只有一半的租金,由于用户满意度是强制链接到转化率,从而直接转化为产生的收入的。前端

Sourcejquery

关于页面加载时间对客户满意度和转换率的影响,有不少 研究 支持这种说法。例如,Aberdeen Group 发现,额外延迟的 1 秒会致使页面浏览量减小 11%,转化次数损失 7%。 但你也能够询问 Google Amazon,他们会告诉你一样的说法。git

怎样让网站加速

为初创公司 Thinks 搭建的网上商城参与了 DHDL,并在 9 月 6 日播出。咱们面临着一个挑战,搭建一个可以承受数十万访客量的网上商店,而且加载时间稳定在 1 秒之内。如下都是咱们在这个过程当中以及从近些年对数据库和网络的性能研究中学到的。github

在现有的 web 应用技术中有三个影响页面加载时间的主要缘由,展现以下:web

  1. 后端处理:web 服务器须要时间从数据库加载数据和整合网站。
  2. 网络延迟:每一个请求须要时间从客户端传输到服务器,并返回(请求延迟)。当考虑到平均每一个网站须要发出超过 100 个请求 才能彻底加载时,这变得更加剧要。
  3. 前端处理:前端设备须要时间来渲染页面。

为了让咱们的网店加速,让咱们一一解决这三个瓶颈。算法

前端性能

影响前端性能最重要的因素是关键呈现路径(CRP),它描述了在浏览器中向用户显示页面所需的 5 个必要步骤,以下所示。sql

关键呈现路径的步骤:

  • DOM:当浏览器解析HTML时,它会增量式地生成一个 HTML 标签的树模型,称为 文档对象模型(DOM),该模型描述了页面内容。
  • CSSOM:一旦浏览器接收到全部的 CSS,它会生成一个 CSS 中包含的标签和类的树模型,称为 CSS 对象模型,在树节点上还附有样式信息。这棵树描述了页面内容是如何设置样式的。
  • 渲染树:经过组合 DOM 和 CSSOM,浏览器构造一个渲染树,它包含页面内容以及要应用的样式信息。
  • 布局:布局这一步计算屏幕上页面内容的实际位置和大小。
  • 绘制:最后一步使用布局信息将实际像素绘制到屏幕上。

单个步骤是至关简单的,使事情变得困难并限制性能的是这些步骤之间的依赖。DOM 和 CSSOM 的构造一般具备最大的性能影响。

这个图表显示了关键呈现路径的步骤,里面包括等待依赖,如箭头所示。

关系呈现路径中重要的依赖

在加载 CSS 和构造完整的 CSSOM 以前,什么都不能显示给客户端。所以 CSS 被称为是阻塞渲染的。

JavaScript(JS)更糟糕,由于它能够访问和更改 DOM 和 CSSOM。 这意味着一旦发现 HTML 中的脚本标记,DOM 构造就会被暂停,并从服务器请求脚本。一旦脚本被加载,只有在全部 CSS 被提取和 CSSOM 被构造之后,它才能被执行。在 CSSOM 构建以后 JS 被执行,在下面的例子中,它能够访问和改变 DOM 以及 CSSOM。只有这样以后,DOM的构造才能进行,而且页面才能显示给客户端。所以 JavaScript 被称为是阻塞解析的。

JavaScript 访问 CSSOM 和更改 DOM 的示例:

<script>
   ...
   var old = elem.style.width;
   elem.style.width = "50px";
   document.write("alter DOM");
   ...
</script>

JS 甚至会影响更恶劣。例如 jQuery 插件 访问计算后的 HTML 元素的布局信息,而后开始一次又一次地改变 CSSOM,直到实现了所需的布局。所以,在用户将看到白色屏幕之外的任何东西以前,浏览器必须一次又一次地重复地执行 JS、构造渲染树和布局。

有三个优化 CRP 的 基本概念

  1. 减小关键资源: 关键资源是页面最初渲染时所需的资源(HTML,CSS,JS 文件)。经过将渲染不滚动时可见的网站部分(称为首屏)所须要的 CSS 和 JS 内联能够大大减小关键资源。接下来的 JS 和 CSS 应该被异步加载。没法被异步加载的文件能够拼接到一个文件中。
  2. 最小化字节: 经过最小化压缩 CSS,JS 和图像,能够大大减小 CRP 中加载的字节数。
  3. 缩短 CRP 长度: CRP 长度是获取全部关键资源所需的与服务器之间的最大连续往返数。它能够经过减小关键资源和最小化它们的大小(大文件须要多个往返来获取)来缩短。将 CSS 放在 HTML 顶部,以及 JS 放在 HTML 底部,能够进一步地缩短它的长度,由于 JS 执行老是会阻塞对 CSS 的抓取、对 CSSOM 和 DOM 的构造。

此外,浏览器缓存 是很是有效的,应该在全部的项目中加以使用。它对于这三个优化项都很合适,由于缓存的资源没必要先从服务器加载。

CRP 优化的整个主题是至关复杂的,特别是内联、级联和异步加载,它们可能会破坏代码的可重用性。幸运的是,有不少强大的工具,能够为你作好这些优化,这些工具能够被集成到你的构建和部署链里。你的确应该地看看下面的工具……

有了这些工具只需很小的工做量,你就能够打造一个前端性能极好的网站。这里是 Thinks 商城第一次访问时的页面速度测试:

thinks.com 的 Google 网页速度分数

有趣的是,PageSpeed Insights 内部惟一的抱怨是,Google 分析的脚本缓存生命周期过短。因此 Google 基本上在抱怨它本身。

来自加拿大(GTmetrix)的第一次页面加载,服务器托管在法兰克福(Frankfurt)

网络性能

网络延迟是页面加载时间最重要的因素,它也是最难优化的。但在咱们进行优化以前,让咱们看一下对初始的浏览器请求的划分:

当咱们在浏览器中输入 https://www.thinks.com/ 并按下回车键时,浏览器开始使用 DNS 查找来识别与域相关联的 IP 地址,这种查找必须对每一个单独的域进行。

使用接收到的 IP 地址,浏览器初始化与服务器的 TCP 链接。TCP 握手须要 2 次往返(1 次是 TCP 快速打开)。使用安全的 SSL 链接,TLS 握手须要额外的 2 次往返(1 次是 TLS False Start Session Resumption)。

在初始链接以后,浏览器发送实际请求并等待数据进入。第一个字节到达的时间主要取决于客户端和服务器之间的距离,包括服务器渲染页面所需的时间(包括会话查找、数据库查询和模板渲染等)。

最后一步是在可能的屡次往返中下载资源(在这种状况下指的是 HTML )。新链接尤为一般须要不少往返,由于初始拥塞窗口很小。这意味着 TCP 不是从一开始就使用全带宽,而是随着时间的推移而增长带宽(参见 TCP拥塞控制。下载速度受到慢启动算法的支配,该算法在每次往返的拥塞窗口中将报文段数量加倍,直到丢包发生。在移动网络和 Wifi 网络上丢失数据包所以具备很大的性能影响。

另外一件要记住的事是:使用 HTTP/1.1,你只能获得 6 个并行链接(若是浏览器仍然遵循原始标准,则链接数为 2)。所以,你最多只能请求 6 个资源并行。

为了对网络性能对于页面速度的重要性有一个直观的认识,你能够查看 httparchive ,上面有不少统计数据。例如,网站平均在 100 多个请求中加载大约 2.5 MB的数据。

来源

因此网站发出了不少小的请求来加载不少资源,但网络带宽一直在增长。物理网络的演进将拯救咱们,对吧?嗯,其实并非……

来自 High Performance Browser Networking,做者为 Ilya Grigorik

事实证实,将带宽增长到 5 Mbps 以上并不真的影响页面加载时间。但减小单个请求的延迟会下降网页加载时间。这意味着带宽加倍带来的是相同的加载时间,而减小一半的延迟将给你一半的加载时间。

所以,若是延迟是网络性能的决定因素,咱们能够在这上面作些什么呢?

  • 持久链接是必须有的。没有什么比当你的服务器在每一个请求后关闭链接,而且浏览器必须一次又一次地执行握手操做和 TCP 慢启动更糟糕的事情了。
  • 尽量地避免重定向,由于它们会大大减慢你的初始网页加载速度。永远连接完整的网址(例如使用 www.thinks.com 而不是 thinks.com)。
  • 若是能够的话,请使用 HTTP/2。它附带服务器推送,能为单个请求传输多个资源;头压缩来减少请求和响应的大小;并请求流水线多路复用经过单个链接发送任意并行请求。使用服务器推送,你的服务器能够发送你的 html ,紧接着推送网站所需的 CSS 和 JS,而无需等待实际请求。
  • 为你的静态资源(CSS,JS,静态图像如 logo)设置显式的缓存头。这样,你能够告诉浏览器须要将这些资源缓存多长时间以及什么时候从新验证。缓存能够节省大量的往返和须要下载的字节。若是没有设置明确的缓存头,浏览器会作 启发式缓存,这比不缓存好,但远不是最佳。

  • 使用内容分发网络(CDN)来缓存图像、CSS、JS 和 HTML。这些分布式缓存网络能够显著地减小与用户的距离,从而更快地提供资源。它们还加速了你的初始链接,由于你与附近的 CDN 节点进行 TCP 和 TLS 握手,而这些节点会依次创建热的和持久的后端链接。

  • 建议你使用一个小的初始页来建立单页应用程序,这个初始网页会异步地加载其它组件。这样,你可使用可缓存的 HTML 模板,在小请求中加载动态数据,并在导航(navigation)期间只更新页面的各个部分。

总而言之,当涉及到网络性能时,有一些要作的(do) 和不要作的(don't),但限制因素老是往返次数与物理网络延迟的结合。克服这种限制的惟一有效方法是使数据更接近客户端。最早进的网络缓存状态的确如此,但这仅适用于静态资源。

对于 Thinks,咱们遵循上述准则,使用 Fastly CDN 和主动的浏览器缓存,甚至对动态数据使用一种新的 布隆过滤器算法(Bloom Filter algorithm) 来使得缓存数据保持一致。

www.thinks.com 重复加载,来显示浏览器缓存覆盖率

对于重复网页加载的请求,浏览器缓存没有提供的内容(参见上图)包括:对 Google 分析的 API 的两个异步调用,以及从 CDN 处获取的初始 HTML 请求。所以,对于重复的网页加载,页面可以作到当即加载。

后端性能

对于后端性能,咱们须要同时考虑延迟和吞吐量。为了实现低延迟,咱们须要将服务器的处理时间最小化。为了保持高吞吐量和应对负载尖峰,咱们须要采用一种水平可扩展的架构。咱们不会谈到太多细节,由于设计决策对性能的影响空间是巨大的,这些是须要去寻找的最重要的组件和属性:

可扩展的后端技术栈组件:负载均衡器,无状态应用服务器,分布式数据库

首先,你须要负载均衡(例如 Amazon ELB 或 DNS 负载均衡)将传入的请求分配给你的一个应用服务器。它还应该实现自动调节功能,在须要时生成其余应用服务器,以及故障转移功能,以替换损坏的服务器并将请求从新路由到正常服务器。

应用服务器将共享状态最小化,从而保持协调最少,并使用无状态会话处理来启用自由的负载均衡。此外,服务器应该有高效的代码和 IO,使得服务器处理时间最小。

数据库须要承受负载尖峰,并尽量减小处理时间。同时,它们须要具备足够的表达性,以根据须要建模和查询数据。有大量的可扩展数据库(尤为是 NoSQL),每一个都有本身的 trade-off。详细信息请参考咱们关于该主题的调查和决策指南:

NoSQL 数据库:一份调查和决策指南
与咱们在汉堡大学的同事一块儿,咱们是:Felix Gessert, Wolfram Wingerath, Steffen…medium.baqend.com

Thinks 网上商城搭建在 Baqend 上,使用了以下的后端技术栈:

Baqend的后端技术栈:MongoDB 做为主数据库,无状态应用服务器,HTTP 缓存层次结构,REST 和 web 前端的 JS SDK

用于 Thinks 的主数据库是 MongoDB。为了维护咱们将要到期的布隆过滤器(用于浏览器缓存),咱们使用 Redis ,由于它的高写入吞吐量。无状态应用程服务器(Orestes Servers)为后端功能提供接口(文件托管,数据存储,实时查询,推送通知,访问控制等),并处理动态数据的缓存一致性。它们从 CDN 拿到请求,CDN 也充当负载均衡器。网站前端使用基于 REST API JS SDK 来访问后端,后端自动利用完整的 HTTP 缓存层次结构来让请求加速并保持缓存数据时刻最新。

负载测试

为了在高负载下测试 Thinks 网上商城,咱们在法兰克福的 t2.medium AWS 实例上使用 2 个应用服务器来进行负载测试。MongoDB 在两个 t2.large 实例上运行。使用 JMeter 构建负载测试并在 IBM soft layer 上的 20 个机器上运行,以模拟在 15分钟内200,000 个用户同时访问和浏览网站。20% 的用户(40,000)被配置为执行额外的付款流程。

网上商城的负载测试设置

咱们在支付实现中发现了一些瓶颈,例如,咱们必须从库存的积极更新(使用 findAndModify实现)切换到 MongoDB 的部分更新操做(inc)。可是在这以后,服务器处理的负载只是精细地达到了平均请求延迟 5 ms

JMeter 在负载测试期间输出:在 12 分钟内有 680 万个请求,平均延迟 5 ms

全部的负载测试组合生成了大约 1000 万个请求,传输了 460 GB的数据,伴随着 99.8% 的 CDN 缓存命中率

负载测试后的仪表板概述

总结

总之,良好的用户体验取决于三个支柱:前端,网络和后端的性能。

前端性能是咱们认为最容易实现的,由于已经有不少工具和一些容易遵循的最佳实践。但仍然有不少网站不遵循这些最佳实践,彻底没有优化过它们的前端。

网络性能对于页面加载时间来讲,是最重要的因素,也是最难优化的。缓存和 CDN 是最有效的优化方法,但即便对于静态内容也要付出至关大的努力。

后端性能取决于单服务器性能和跨机器去分发工做的能力。水平可扩展性特别难以实现,必须从一开始就考虑。许多项目将可扩展性和性能做为过后处理,然而在它们的业务增加时会陷入大麻烦。

文献和工具建议

有不少关于 web 性能和可扩展系统设计的书:由 Ilya Grigorik 所写的 高性能浏览器网络 包含了几乎全部你须要了解的网络和浏览器性能知识,而且目前不断更新的版本能够免费在线阅读哦!Martin Kleppmann 写的 设计数据密集型应用 仍处于前期发布状态,但已是其领域最好的书之一,它涵盖了可扩展后端系统背后的大部分基础知识,并拥有至关多的细节。设计性能 由Lara Callender Hogan 写成,围绕着构建快速的、具备良好的用户体验的网站,涵盖了不少最佳实践。

还有一些很棒的在线指南、教程和工具能够考虑:从初学者友好的 Udacity 课程 网站性能优化、Google 的 开发者性能指南 到相似于 Google PageSpeed InsightsGTmetrixWebPageTest 这样的优化工具。

最新的 Web 性能开发

移动页面加速

Google 正在经过诸如 PageSpeed Insights开发人员指南 等网站性能项目来提升你们对于网站性能的意识,并将网页速度做为其 网页排名 的主要因素。

在 Google 搜索中用来提升网页速度、加强用户体验的最新概念是 移动网页加速(AMP。其目的是让新闻文章、产品页面和其它搜索内容当即从 Google 搜索加载。为此,这些页面必须构建为 AMP。

一个 AMP 页面的示例

AMP 主要作两件事:

  1. 构建为 AMP 的网站使用精简版本的 HTML,并使用 JS 加载器来快速渲染,并异步加载尽量多的资源。

  2. Google 将网站缓存在 Google CDN 中,并经过 HTTP/2 分发。

第一件事从本质上意味着 AMP 以一种方式限制了你的 HTML、JS 和 CSS,这种方式构建的网页有一个优化的关键呈现路径,能够很容易地被 Google 爬取。 AMP 强制 几个限制,例如全部 CSS 必须内联,全部 JS 必须是异步的,页面上的全部内容必须具备静态大小(以防止重绘)。 虽然你能够经过坚持以前的 web 性能最佳实践,在没有这些限制的状况下,实现相同的结果,但 AMP 多是很好的 trade-off ,可以为很是简单的网站提供帮助。

第二件事意味着,Google 抓取你的网站,而后将其缓存在 Google CDN 中,以便快速分发。网站内容会在爬虫从新索引你的网站后更新。CDN 还遵循服务器设置的静态 TTL,但至少执行 微缓存:资源至少在一分钟内被视为最新的,并在用户请求进入时在后台更新。所以 AMP 最适用于内容大可能是静态的用户案例。这种适用于人为编辑修改的新闻网站或者其余出版物的状况。

渐进式 web 应用(Progressive Web Apps)

Google 的另外一种作法是 渐进式 web 应用PWA)。其想法是在浏览器中使用 服务工做者(service worker) 来缓存网站的静态部分。所以,这些部分对于重复视图会当即加载,并可离线使用。动态部分仍从服务器端加载。

app shell(单页应用程序逻辑)能够在后台从新验证。若是标识了对应用 shell 的更新,则会提示用户,要求他更新页面。例如,Gmail 收件箱 就实现了这个。

可是,写出缓存静态资源并进行从新验证的服务工做者(service worker)代码,对于每一个网站来讲,都须要付出至关大的努力。此外,只有 Chrome 和 Firefox 充分地支持了服务工做者(service worker)。

缓存动态内容

全部缓存方法遇到的问题是它们不能处理动态内容。这只是因为 HTTP 缓存的工做机制致使的。有两种类型的缓存:基于失效的缓存(如转发代理缓存和 CDN)和基于到期的缓存(如 ISP 缓存、机构代理和浏览器缓存)。基于失效的缓存能够从服务器端主动失效,基于到期的高速缓存只能从客户端从新验证。

使用基于到期的缓存时,棘手的事情是,你必须在首次从服务器拿到数据时指定缓存生命周期(TTL)。以后,你没有任何机会将缓存数据删除。它将由浏览器缓存提供到 TTL 到期的时刻。对于静态资源,这不是一件复杂的事情,由于它们一般只会在你部署 web 应用程序的新版本时发生变化。所以,你可使用 gulp-rev-allgrunt-filerev 等很酷的工具)对 assets 进行散列。

可是,可是你该如何处理运行时的应用数据加载和修改呢?更改用户我的资料、更新帖子或添加新评论彷佛不可能与浏览器缓存结合使用,由于你没法预估此类更新未来什么时候会发生。所以,缓存只能被禁用或使用很是小的 TTL。

由另外一个客户端更新时,缓存动态数据如何过期的示例

Baqend 的 Cache-Sketch 方法

Baqend,咱们已经研究并开发了一种方法,在实际获取以前,检查客户端中 URL 的陈旧度。在每一个用户会话开始时,咱们获取一个很是小的数据结构,称为布隆过滤器(Bloom Filter),它是全部过期资源集合的高度压缩表示。经过查看布隆过滤器,客户端能够检查资源是否过期(包含在布隆过滤器中)或者是不是全新的。对于潜在的过期资源,咱们绕过浏览器缓存并从 CDN 获取内容。在其余的全部状况下,咱们直接用浏览器缓存提供内容。使用浏览器缓存能够节省网络流量和带宽,而且是很快的

此外,咱们确保 CDN(以及其它基于失效的缓存,如 Varnish)始终包含最新的数据,只要它们过期就当即清除资源。

Baqend 如何确保缓存动态数据的新鲜度示例

布隆过滤器(Bloom filter) 是具备可调误报率的几率数据结构,这意味着集合能够用来表示对从未添加的对象的遏制,但永远不会删除实际条目。换句话说,咱们可能偶尔会从新验证新资源,可是咱们永远不会提供过时数据。注意,误报率很是低,这使得咱们可以让集合很是小。例如,咱们只须要 11 Kbyte 来存储 20,000 个不一样的更新。

Baqend 在服务器端有不少流处理(查询匹配检测)、机器学习(最佳 TTL 估计)和分布式协调(可扩展的布隆过滤器维护)的工做。若是你对这些细节感兴趣,看看这篇 文章 这些幻灯片 来深刻研究。

性能收益

这一切都归结为这一点。

使用 Baqend 的缓存基础设施可使哪一种页面速度获得提升?

为了展现使用 Baqend 的好处,咱们在后端即服务(BaaS)领域中的每一个领先竞争对手上构建了一个很是简单的新闻应用,并观测了来自世界各地不一样位置的页面加载时间。以下所示,Baqend 持续加载低于 1 秒,比平均速度快 6.8 倍。即便当全部客户端来自服务器所在的同一位置时,因为有浏览器缓存,Baqend 也是 150% 倍速度。

简单新闻应用的平均加载时间比较

咱们将此比较做为一个 动手的 web 应用 来比较 BaaS 竞争。

动手比较 的截图

但这固然是一个测试场景,而不是一个具备真正用户的 web 应用。 因此让咱们回到 Thinks 网上商城来看一个真实世界的例子。

Thinks 网上商城——全部的事实

当 DHDL("Shark Tank"的德国版)在 9 月 6 日播出时,有 270 万观众,咱们坐在电视和咱们的 Google 分析屏幕前,为 Thinks 创始人提出他们的产品而激动。

从他们开始演示起,网上商的并发用户数量迅速增长到大约 10,000,但真正的巅峰发生在广告休息时,当时忽然有超过45,000 的并发用户来参观该店购买 Towell+:

Google 分析观测在商业广告时间以前开始。

Thinks 在电视播放的 30 分钟里,咱们获得了 340 万的请求,300,000 位游客,高达 50,000 位的并发访问游客和高达每秒 20,000 个请求,全部这一切实现了在 CDN 级别的 98.5% 的缓存命中率,和平均为 3% 的服务器 CPU 负载

所以,页面加载时间低于 1 秒,整个时间实现了 7.8% 的极大的转化率。

若是咱们看看在同一集 DHDL 中展现的其余商城,咱们会看到其中四个 彻底崩溃了,剩下的商城只利用了极少的性能优化。

可用性概述和商城的 Google 页面速度得分,在 DHDL 上,于 9 月 6 日展现。

总结

咱们已经看到了在设计快速和可扩展的网站时须要克服的瓶颈:咱们必须掌握关键呈现路径,理解网络限制、缓存的重要性和具备水平可扩展性的后端设计。

咱们已经看到了不少用来解决单个问题的工具,以及移动加速页面(AMP)和渐进式 web 应用(PWA),这些采起了更全面的作法。可是,缓存动态数据的问题仍然存在。

Baqend 的作法是减小 web 开发,将构建主要放在前端,经过 JS SDK 使用 Baqend 彻底托管的云服务上的后端功能,包括数据和文件存储、(实时)查询、推送通知、用户管理和 OAuth 以及访问控制。该平台经过使用完整的 HTTP 缓存层次结构自动加速全部请求,并确保可用性和可扩展性。

咱们对于 Baqend 的愿景是一个不须要加载时间的网站,而且咱们想要给你到达这个目标的工具。

继续前往免费试用 www.baqend.com.


不想错过咱们关于网络性能的下一篇文章?经过加入咱们的 newsletter 方便地将其传送到你的收件箱。