Categories
程式開發

我们怎样将官网的加载时间缩短1.7秒?


我们怎样将官网的加载时间缩短1.7秒? 1

本文最初发布于Casper技术博客,经原作者授权由InfoQ中文站翻译并分享。

我们在casper.com上部署了一个变更,从我们自己的服务器而非供应商的服务器上加载一段第三方JavaScript代码。这个改动将初始渲染时间缩短了1.7秒:

我们怎样将官网的加载时间缩短1.7秒? 2

在桌面Chrome w/3G网络下测试的数据

上文说的第三方JavaScript来自一家名为Optimizely的公司。通过使用他们的客户端JavaScript,我们在casper.com上进行a/b测试。一旦JavaScript文件下载完成并执行,它将改变网站一半访客的文档,从而度量他们对变化的反应。为确保尽可能地避免文档样式短暂失效(FOUC),我们遵循的最佳实践是以阻塞方式最先加载Optimizely。

正如我们所预期,采用这种方式加载JavaScript代码段会对我们网站的Web性能产生负面影响。

为此,我们在很长一段时间进行权衡。

我们是应该遵循Web性能最佳实践并异步加载Optimizely JavaScript,还是遵循最佳实践的实验以阻塞方式加载?无论哪种,各有利弊。

我们想到的一个好方法是自托管Optimizely代码段。一般,像Optimizely这样的供应商会提供一个JavaScript文件的URL(由他们托管)。问题是,这会导致新的DNS查询以及与供应商服务器之间新的HTTP连接和SSL握手。

用这种方式加载的另一项成本是,无法使用HTTP2多路复用来提供资产,而对于浏览器和服务器来说,这是一种更有效的通信方式。如下面的截图所示,从我们一项性能测试中可以看出,这会导致DNS查询延迟39ms,建立服务器连接延迟54ms,SSL握手延迟135ms。

此外,在等待第一个字节时有175ms的延迟,如果我们能使用HTTP2多路复用,就可以消除这一延迟。

我们怎样将官网的加载时间缩短1.7秒? 3

自托管文件的最后一个好处是,我们能更好地控制边缘(CDN)和客户端(浏览器)缓存。Optimizely不会让你控制它们的边缘缓存,但它们可以让你控制客户端缓存。有一个设置允许你配置cache-control值,我们将其设置为2分钟。对我们而言,当文件由Optimizely托管时,这是一个理想设置。

为了证明自托管更好,我们手动复制了Optimizely JavaScript文件的内容,并在服务器上保存了一个版本,同时,替换staging环境中的引用,指向我们的自托管版本。结果并不明显。这让人相当失望,以至于我们的数据分析师说,为了在初始渲染时间上节省200毫秒,不值得做这么多事。对这个说法,我们一致同意。

我们怎样将官网的加载时间缩短1.7秒? 4

我们一直在坚持,因为我们认为staging环境不是很适合测试这种性能变化。我们的staging环境缺少很多只在生产环境中运行的第三方JavaScript。所以我们设计了一个产品测试,在这个测试中,我们部署Optimizely的静态自托管版本,而数据分析师在3天内不对Optimizely做任何更改。

我们怎样将官网的加载时间缩短1.7秒? 5

下降的时段是在Optimizely的自托管版本部署到生产环境期间(测量环境为桌面Chrome、有线网络连接——当时我们没有在3G网络的速度下测量性能,这就是为什么文章一开始的图里效果更明显,但现在3G是我们测量时的标准网速)

从上图可以看到,当Optimizely代码段的自托管静态版本在生产环境中运行时,初始渲染时间出现下降。通过自托管,由于我们消除了DNS查询、Optimizely 服务器连接、SSL握手、首字节时间,并启用了H2多路复用,所以初始渲染时间大大减少。

不过,我们还没有做好永久改变的准备。Optimizely的工作方式是,如果对实验做了更改,JavaScript代码段会在Optimizely服务器上更新。更改可能是开始/暂停一个实验,修改一个实验等等。你所做的任何更改都会生成新版本的JavaScript文件。

因为只是在生产环境中加载了手动复制的JavaScript文件的静态副本,所以我们不能一直保存它,因为那样我们就永远无法开始/暂停实验。对软件工程师来说,每次更改时都要手动复制新文件,这相当麻烦。

所以,既然我们看到这种方法的好处,就必须弄清楚如何从我们自己的服务器动态加载最新版本的Optimizely代码段。

我们怎样将官网的加载时间缩短1.7秒? 6

为此,我们创建了一个每60秒运行一次的AWS Lambda。当运行时,它会向optimizely.com发送一个JavaScript文件请求。它创建文件的散列,并检查S3以确定散列是否变化(我们将上次执行时的散列存储在S3上的一个文件里)。如果散列发生变化,则将新的JavaScript文件保存到S3,文件名中包含散列的一部分(例如:snippet-c36d504bc3c26479f1181e6119617a64.js)。接下来,Lambda将散列发送给我们Fastly边缘服务器上的一个字典。这就是奇妙之处。我们将边缘服务器配置为ESI(Edge Side Include)和边缘字典的组合,动态地将最新的Optimizely JavaScript文件名插入到边缘服务器提供的每个页面的HTML中。这让我们可以在边缘处更新对Optimizely文件的引用,而不必每次文件更改时都重新部署网站。

以下是WebPageTest的截图,它测量的是由Casper托管的新Optimizely文件的性能:

我们怎样将官网的加载时间缩短1.7秒? 7

下面是通过WebPageTest收集到的自托管前后的数据对比:

我们怎样将官网的加载时间缩短1.7秒? 8

理想情况下,对于这些值,我们会提供实际用户监控(RUM)的95百分位数据,但对于casper.com,我们还没有完全实现这一点。据推测,Optimizely托管的时间(我们不确定是好是坏)和Casper托管内容的下载时间会有一些波动,因为这是合成测试。

这是一个Chrome瀑布流,显示了在casper.com和Optimizely文件上运用HTTP2多路复用的效果。请注意,前5个资产的内容下载几乎是在同一时间开始的。

我们怎样将官网的加载时间缩短1.7秒? 9

最后,如前所述,自托管让我们能更好地控制缓存。我们将边缘服务器配置为将文件在边缘和浏览器缓存中保存整整一年。之所以能这样做,是因为文件名对于内容是惟一的(我们将文件散列的一部分添加到文件名中),并在内容更改时替换对文件名的引用。这样,如果我们不对Optimizely代码片段做任何修改,重复访客的浏览器甚至不会向casper.com请求文件。相反,它将直接从用户文件系统的缓存中提取文件。超级快!

我们怎样将官网的加载时间缩短1.7秒? 10

如下图所示,你可以看到从浏览器缓存提供文件的好处:

我们怎样将官网的加载时间缩短1.7秒? 11

这种方法的缺点是,当我们频繁地修改Optimizely的代码段时,网站访问者将无法体验到最优缓存。随着业务增长,我们的数据分析师可能会运行更多的a/b测试,这就需要频繁地修改文件。这可能导致网站访问者在访问casper.com期间需要下载多个版本的文件。我们在一个自定义的DataDog仪表盘跟踪JavaScript文件的每次修改:

我们怎样将官网的加载时间缩短1.7秒? 12

在这个图表中,我们可以看到,在23日3个小时的时间里,代码段更改了大约25次。由于我们的平均访问持续时间不是很长,因此,不太可能有大量的访问者以这种更改频率下载代码段的多个版本。

总的来说,我们认为自托管的好处多于缺点。

我们的软件工程师、产品经理、站点可靠性工程师和数据分析师经过大约一个月断断续续的工作完成了这个项目。

英文原文:

How we shaved 1.7 seconds off casper.com by self-hosting Optimizely