写点什么

我们怎样将官网的加载时间缩短 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:002402
用户头像

发布了 816 篇内容, 共 561.4 次阅读, 收获喜欢 1590 次。

关注

评论

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

AI算力加速之道

安第斯智能云

人工智能 深度学习 异构计算

这场汇聚行业顶级大咖的Meetup,有哪些不容错过的干货?| IDP Meetup 01

Baihai IDP

人工智能 AI 生态 Meetup

Linux之ss命令

入门小站

Linux

31 家企业入选阿里云首期云原生加速器,共建云原生行业新生态

阿里巴巴云原生

阿里云 云原生 云原生加速器 招募 行业生态

信息安全无小事!教你如何做日志脱敏

卢永德

优雅的DO,VO,DTO转换工具MapStruct

卢永德

3月月更

作为前端上传图片的两种方法

小学僧

3月程序媛福利 3月月更

在 Nebula K8s 集群中使用 nebula-spark-connector 和 nebula-algorithm

NebulaGraph

数据库 开源 分布式系统 图数据库

恒源云(GpuShare)_加速pytorch训练的方法来喽~

恒源云

深度学习 PyTorch

尚硅谷Maven新版视频教程发布

@零度

maven

一文带你了解动态数组方法实现

xiaoyu

数据结构 算法 数组 java; 动态数组

大数据Flink培训:维表Join/双流Join的方法

@零度

flink join 大数据开发

CNI 这么多,怎么选?| 容器网络系列第1期

BoCloud博云

Kubernetes 容器云 cni 容器网络平台

豆瓣9.6分,Scala编程圣经第5版重磅来袭!

博文视点Broadview

OpenHarmony开源开发者成长计划 | 知识赋能第四期课程——标准系统HDF开发

Anna

直播 赋能 OpenHarmony

云原生小课堂 | Envoy请求流程源码解析(二):请求解析

York

云原生 istio 网络 envoy Service Mesh (ASM)

HarmonyOS UI组件在线预览,程序员直呼“不要太方便~”

HarmonyOS开发者

UI HarmonyOS

程序设计优化之管道数据流

有道技术团队

java培训:Redis和Mysql数据怎么保持一致

@零度

MySQL redis JAVA开发

虎符交易所完成2月HOO回购 HOO单月涨幅高达40%

区块链前沿News

Hoo 虎符交易所 平台币

理解Restful风格

蜜糖的代码注释

RESTful 接口规范 REST API 3月月更

upload上传文件类型的限制的几种方式(element)

lo

JavaScript

优雅的编码习惯总是让人心情愉悦(Shell篇)

XinXing

Shell Code 优雅 脚本 规范

AHPA:开启 Kubernetes 弹性预测之门

阿里巴巴云原生

阿里云 Kubernetes 云原生 AHPA 弹性预测

黄东旭当选 CCF 数据库专业委员会、开源发展委员会、大数据专家委员会执行委员

PingCAP

面试官:对于宏任务和微任务,你知道多少?

是乃德也是Ned

JavaScript 面试 前端 ES6 Promise

Go学习笔记——条件判断if

为自己带盐

Go 学习笔记 3月月更

web前端培训:react基础面试题分享

@零度

前端开发 React

声网崩溃数据的自动化闭环处理

声网

自动化 测试 Dev for Dev

Java 8 Stream 从入门到进阶——像SQL一样玩转集合

Geek_rze78a

Java JAVA stream

web技术分享| WebRTC控制摄像机平移、倾斜和缩放

anyRTC开发者

前端 音视频 WebRTC 摄像头 web技术分享

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