写点什么

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

  • 2020-05-16
  • 本文字数:2701 字

    阅读完需:约 9 分钟

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


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


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



在桌面 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 多路复用,就可以消除这一延迟。



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


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



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



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


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


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


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


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



为此,我们创建了一个每 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 文件的性能:



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



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


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



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



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



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



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


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


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


英文原文:


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


2020-05-16 10:002383
用户头像

发布了 806 篇内容, 共 552.9 次阅读, 收获喜欢 1585 次。

关注

评论

发布
暂无评论
发现更多内容

【算法技术专题】如何用Java实现一致性 hash 算法( consistent hashing )(上)

码界西柚

算法 一致性hash 11月日更

Ubuntu系统下《汇编语言》环境配置

codists

汇编语言

进击的Java(七)

ES_her0

11月日更

数据库连接池Demo(1)单线程初步

Java 数据库 连接池

腾讯云原生开源生态专场召开,洞察开源云原生技术发展趋势和商业化路径

腾源会

腾讯云 开源 云原生

这一次,Google 终于对 Web 自动化下手了!

星安果

chrome 自动化

Golang Gin 框架入门介绍(二)

liuzhen007

11月日更

多模态内容理解算法框架项目 Lichee 正式开源,为微服务开源社区贡献力量

腾源会

开源

腾讯发布 K8s 多集群管理开源项目 Clusternet

腾源会

开源 K8s 多集群管理 Clusternet

腾讯自研分布式远程Shuffle服务Firestorm正式开源

腾源会

大数据 开源 腾讯

在华为云专属月,找到开启互联网第二增长曲线的一把钥匙

脑极体

架构训练营 模块三 作业

dog_brother

「架构实战营」

怎么清空.NET数据库连接池

喵叔

11月日更

SuperEdge 和 FabEdge 联合在边缘 K8s 集群支持原生 Service 云边互访和 PodIP 直通

腾源会

开源 边缘计算 superedge

中央银行、不平等和新技术:使用分布式账本、可编程合约和密码学的蓝图

CECBC

Android C++系列:JNI操作Bitmap

轻口味

c++ android jni 11月日更

【高并发】通过源码深度解析ThreadPoolExecutor类是如何保证线程池正确运行的

冰河

Java 并发编程 多线程 高并发 异步编程

消息队列表设计

Rabbit

请问软件测试和渗透测试的区别是什么?

喀拉峻

网络安全 渗透测试

验证码

卢卡多多

图片验证码 11月日更

你不知道的开源分布式存储系统 Alluxio 源码完整解析(下篇)

腾源会

大数据 开源

如何评价一个开源项目(一)--活跃度

腾源会

开源

Prometeus 2.31.0 新特性

耳东@Erdong

release Prometheus 11月日更

[ CloudWeGo 微服务实践 - 08 ] Nacos 服务发现扩展 (2)

baiyutang

golang 微服务 11月日更

15 K8S之容器安全上下文

穿过生命散发芬芳

k8s 11月日更

范学雷的专栏《深入剖析 Java 新特性》

IT蜗壳-Tango

11月日更

腾讯开源全景图再刷新:社区贡献领跑国内企业,获超过38万开发者关注

腾源会

开源 腾讯

我在 IBM 从事开源工作的十一年

腾源会

开源

一文告诉你 K8s PR (Pull Request) 怎样才能被 merge?

腾源会

k8s

CNCF 沙箱再添“新将”!云原生边缘容器开源项目 SuperEdge 正式入选

腾源会

开源 容器 云原生 cncf

科技热点周刊|马斯克套现 440 亿;苹果推出数字身份证;Meta 与微软合作;华为捐赠欧拉

青云技术社区

云计算 物联网

我们怎样将官网的加载时间缩短1.7秒?_文化 & 方法_Kyle Rush_InfoQ精选文章