Instagram.com网站性能优化之路:第一部分

原文:https://instagram-engineering.com/making-instagram-com-faster-part-1-62cc0c327538

作者:Glenn Conner

翻译:奶爸码农

Instagram.com网站性能优化之路:第一部分_第1张图片

近年来,instagram.com发布了许多功能-我们推出了故事,过滤器,创建工具,通知和消息直递,以及许多其他功能和优化。但是,随着产品功能的增长,一个不幸的副作用是我们的网络性能开始下降。在过去的一年中,我们有意识地努力来改善这一状况。到目前为止,我们的不懈努力已使Feed页的加载时间累计提升了近50%。这一系列博客文章将概述我们为实现这些改进所做的一些工作。

Instagram.com网站性能优化之路:第一部分_第2张图片

确定正确的资源下载和执行的优先级,并减少页面加载期间的浏览器卡顿时间是提高Web应用程序性能的主要手段之一。在我们的案例中,其中许多类型的优化被证明比减小代码大小更为直接,减小代码大小通常影响很小,并且只有在进行了许多增量改进后才开始累加(尽管我们将在以后的文章中进行讨论)。它们对产品开发的破坏也较小,需要较少的代码更改和重构。因此,我们开始将精力集中在这一领域-资源预加载

JavaScript, XHR, 和图片预加载

作为一般原则,我们希望尽早通知浏览器加载页面所需的资源。作为开发人员,我们经常提前知道我们将需要哪些资源,但是浏览器可能要等到页面加载过程的后期才意识到这些资源。这些资源主要包括那些由JavaScript动态获取的资源(其他脚本,图像,XHR请求等),因为浏览器只有先解析并执行一些其他JavaScript才能发现这些依赖资源。

无需等待浏览器自己发现这些资源,我们可以向浏览器提示它应该立即开始获取这些资源。我们这样做的方法是使用HTML预加载标签。他们看起来像这样:

"preload" href="my-js-file.js" as="script" type="text/javascript" />

在Instagram,我们将这些预加载提示用于关键页面加载路径上的两种资源:动态加载的JavaScript和预加载XHR GraphQL数据请求。动态加载的脚本是通过import('...')为特定客户端路由加载的脚本。我们维护服务器端入口点和客户端路由脚本之间的映射列表-因此,当我们在服务器端收到页面请求时,我们知道特定服务器端入口点将需要哪些客户端路由脚本,并且我们可以在渲染初始页面HTML时为这些特定于路由的脚本添加预加载。

例如,对于FeedPage入口点,我们知道客户端路由器最终将请求FeedPageContainer.js,因此可以添加以下内容:

"preload" href="/static/FeedPageContainer.js" as="script" type="text/javascript" />

同样,如果我们知道将为特定页面入口点发出特定的GraphQL请求,那么我们应该预加载该XHR请求。这一点特别重要,因为这些GraphQL查询有时可能会花费很长时间,并且在此数据获取之前无法展现页面。因此,我们希望服务器能够在页面生命周期中尽早返回数据。

"preload" href="/graphql/query?id=12345" as="fetch" type="application/json" />

在较慢的连接上,页面加载行为的优化更加明显。通过模拟的快速3G连接(下面的第一个加载瀑布流-没有任何预加载),我们看到FeedPageContainer.js及其关联的GraphQL查询仅在Consumer.js完成加载后才开始。但是,在进行预加载的情况下,只要页面HTML可用,FeedPageContainer.js及其GraphQL查询都可以开始加载。这也减少了加载任何非关键的延迟加载脚本的时间,这可以在第二个瀑布流中看到。这里,FeedSidebarContainer.js和ActivityFeedBox.js(取决于FeedPageContainer.js)几乎在Consumer.js完成之后立即开始加载。

Instagram.com网站性能优化之路:第一部分_第3张图片 Instagram.com网站性能优化之路:第一部分_第4张图片

预加载优先级的好处

除了可以更快地开始下载资源外,链接预加载还具有增加异步脚本下载的网络优先级的额外好处。这对于关键加载路径上的异步脚本而言非常重要,因为这些脚本的默认优先级为“低”。这意味着XHR请求和viewport中的图像将具有更高的网络优先级,而viewport之外的图像将具有相同的网络优先级。这可能导致呈现页面所需的关键脚本被阻止或必须与其他资源共享带宽。谨慎使用可以对我们希望浏览器在初始加载期间如何优先确定内容的优先级(如果我们知道应该优先分配哪些资源)进行控制。

预加载优先级的问题

预加载的问题在于,它提供的额外控制会带来额外条件,即正确设置资源优先级。例如,当在移动和wifi网络非常慢且丢包严重的地区进行测试时,我们注意到脚本的网络请求的优先级高于script标记。关键页面呈现路径上的JavaScript捆绑包数量的增加,导致总页面加载时间增加。这源于我们在页面上布置预加载标签的方式。我们只是为将由客户端路由器作为当前页面的一部分异步下载的捆绑软件提供预加载提示。


"preload" href="SomeConsumerRoute.js" as="script" />
"preload" href="..." as="script" />
...



在登出页面的示例中,我们在Common.js和Consumer.js之前过早下载(预加载)SomeConsumerRoute.js,并且由于预加载的资源以最高优先级下载但并未解析/编译,因此它们阻止了Common&Consumer能够开始解析/编译。Chrome数据保护程序团队还发现了预加载的类似问题,并在此处撰写了有关其解决方案的文章。在他们的情况下,他们选择始终将异步资源的预加载放在请求它们的资源的脚本标记之后。在我们的案例中,我们选择了一种稍微不同的方法。我们决定为所有脚本资源准备一个preload标签,并按照需要的顺序放置它们。这确保了我们能够尽早开始在页面中预加载所有脚本资源(包括直到将某些服务器端数据添加到页面之后才能呈现为HTML的同步脚本标签),并确保我们能够可以控制脚本资源加载的顺序。


"preload" href="Common.js" as="script" />
"preload" href="Consumer.js" as="script" />

"preload" href="SomeConsumerRoute.js" as="script" />
...




图片预加载

instagram.com上的主要页面之一是Feed,其中包含图片和视频的无限滚动Feed流。我们通过加载初始批次的帖子,然后在用户向下滚动Feed时加载其他批次来实现此目的。但是,我们不希望用户每次到达提要的底部时都等待(加载新一批帖子时),因此对于用户体验而言,在用户到达信息流底部之前,先加载新批次对于用户体验而言非常重要。

由于以下几个原因,在实践中很难做到这一点:

  • 我们不希望屏幕外批量加载占用用户当前正在查看的部分Feed的CPU和带宽优先级。

  • 我们不想过分地预加载内容,以免浪费用户带宽,而用户可能再也不需要向下滚动查看内容,但是另一方面,如果预加载不足,则用户经常会在信息流结尾阻塞 。

  • Instagram.com设计为可在各种屏幕尺寸和设备上使用,因此我们使用img srcset属性(可让浏览器根据用户屏幕尺寸决定使用哪种图像分辨率)来显示提要图像。这意味着确定要预加载的图像分辨率并不容易,并且存在预加载浏览器永远不会使用的图像的风险。

我们用于解决该问题的方法是构建一个优先任务,以处理异步工作的排队(在这种情况下,是下一批帖子的预加载)。预加载任务最初以空闲优先级排队(使用requestIdleCallback),因此除非浏览器未做任何其他重要工作,否则它不会开始。但是,如果用户滚动到离当前内容的末尾足够近的时候,我们将通过取消挂起的空闲回调并将此预加载任务立即触发,从而将该预加载任务的优先级提高到“高优先级”。

Instagram.com网站性能优化之路:第一部分_第5张图片

下一批帖子的JSON数据到达后,我们将对该预加载的一批帖子中的所有图像进行顺序的后台预加载排队。我们按照帖子在Feed流中显示的顺序(而不是并行显示)顺序预加载,以便我们可以优先下载和显示最接近用户Viewport的帖子中的图像。为了确保我们实际上预取了正确的图像大小,我们使用了一个隐藏的媒体预取组件,其尺寸与当前Feed匹配。该组件内部是一个,它使用srcset属性,与真实的Feed帖子相同。这意味着我们可以将图像的选择留给浏览器来预取,以确保它使用与渲染真实提要帖子时相同的逻辑来选择正确的图像,并且显示在媒体预取组件上。这也意味着,只要我们使用设置为与目标显示组件相同尺寸的媒体预取组件,就可以预取站点其他表面(例如,个人资料页面)上的图像。

这样做的综合效果是,将照片加载时间减少了25%(将Feed帖子添加到DOM和该帖子中的图像实际完全加载之间的时间),以及将用户在Feed的末尾等待下一页所花费的加载时间减少了56%。

写在最后

这是Instagram网址优化的第一部,在第二部分会介绍如何通过数据推送的方式提升性能,第三部分会介绍基于缓存优先进行渲染,第四部分会介绍优化代码大小和执行优化。请关注奶爸码农公众号,第一时间获得最新信息。

Instagram.com网站性能优化之路:第一部分_第6张图片

奶爸码农』从事互联网研发工作10+年,经历IBM、SAP、陆金所、携程等国内外IT公司,目前在美团负责餐饮相关大前端技术团队,定期分享关于大前端技术、投资理财、个人成长的思考与总结。

推荐阅读:

网站性能指标这么多,你到底选对了吗?

这里有几个编写JavaScript的进阶方法

手把手撸代码实现Virtual Dom && Diff

听听Vue.js作者尤雨溪如何比较前端三大框架

你可能感兴趣的:(Instagram.com网站性能优化之路:第一部分)