Slack客户端中的Service Worker:在下载加速和脱机模式下

该材料(我们今天发布的翻译)专用于优化Slack桌面客户端新版本的故事,其主要特征之一是加速加载。



背景知识


在开始使用新版本的Slack桌面客户端时,创建了一个原型,称为“快速启动”。 您可能会猜到,该原型的目标是尽可能加快下载速度。 使用CDN缓存中的HTML文件,预先存储的Redux存储库数据和服务工作者,我们能够在不到一秒钟的时间内下载客户端的轻量级版本(当时,具有1-2个工作区的用户通常的加载时间约为5秒) 服务人员是这种加速的中心。 此外,他为我们经常要求Slack用户实施的机会铺平了道路。 我们正在谈论客户端的离线模式。 原型使我们能够用一只眼睛从字面上看到翻新的客户能够提供的功能。 基于上述技术,我们开始处理Slack客户端,粗略地想象结果,并专注于加快下载速度和实现程序的脱机模式。 让我们谈谈更新后的客户端的核心如何工作。

什么是服务人员?


本质上, Service Worker是用于网络请求的功能强大的代理对象,它使开发人员可以使用少量JavaScript代码来控制浏览器如何处理单个HTTP请求。 服务人员支持高级且灵活的缓存API,该API的设计目的是将Request对象用作键,而Response对象用作值。 与Web工作人员一样,服务工作人员是在任何浏览器窗口的主要JavaScript代码执行线程之外的自己的进程中执行的。

Service Worker是Application Cache的关注者,现已弃用。 它是由AppCache接口表示的一组API,用于创建实现离线功能的网站。 使用AppCache ,使用了一个静态清单文件,该清单文件描述了开发人员要缓存以供脱机使用的文件。 总的来说, AppCache功能仅限于此。 这种机制很简单,但并不灵活,这没有给开发人员提供对缓存的特殊控制。 在W3C中,在制定Service Worker规范时已将考虑在内。 结果,服务人员允许开发人员管理与Web应用程序或网站执行的每个网络交互会话有关的许多详细信息。

当我们刚开始使用这项技术时,Chrome是唯一支持该技术的浏览器,但是我们知道没有太多时间等待对服务人员的广泛支持。 现在,该技术无处不在 ,所有主流浏览器均支持该技术。

我们如何使用服务人员


当用户首次启动新的Slack客户端时,我们将下载全套资源(HTML,JavaScript,CSS,字体和声音)并将其放置在服务工作者的缓存中。 此外,我们在内存中创建Redux存储的副本,并将此副本写入IndexedDB数据库。 下次启动该程序时,我们检查是否存在相应的缓存。 如果是这样,我们在下载应用程序时会使用它们。 如果用户已连接到Internet,我们将在启动该应用程序后下载最新数据。 如果不是,则客户端保持运行状态。

为了区分以上两种加载客户端的选项,我们给它们起了名字:热(热)和冷(冷)下载。 客户端的冷启动通常是在用户首次启动程序时发生的。 在这种情况下,不会存储任何缓存资源或Redux数据。 通过热启动,我们拥有在用户计算机上运行Slack客户端所需的一切。 请注意,大多数二进制资源(图像,PDF,视频等)都是使用浏览器缓存来处理的(这些资源由常规缓存头控制)。 服务人员不应以特殊方式处理它们,以便我们可以脱机使用它们。


冷热加载之间的选择

服务人员生命周期


服务人员可以处理三个生命周期事件。 这些是安装获取激活 。 下面我们将讨论我们如何应对这些事件,但是首先我们需要讨论下载和注册服务工作者本身。 它的生命周期取决于浏览器如何处理Service Worker文件更新。 这是您可以在MDN上阅读的内容:“如果将下载的文件识别为新文件,则安装完成。 它可以是与现有文件不同的文件(文件的差异是通过按字节比较来确定的),也可以是浏览器在处理页面上首先遇到的服务工作者文件。”

每次我们更新相应的JavaScript,CSS或HTML文件时,都会通过自定义Webpack插件,该插件会创建一个清单,其中包含具有唯一哈希的相应文件的描述( 这是清单文件缩写示例)。 此清单嵌入在服务工作者代码中,该代码使服务工作者在下次启动时进行更新。 而且,即使在服务工作者的实现未更改的情况下,也可以这样做。

▍事件事件


每当服务工作者更新时,我们都会收到install事件。 响应于此,我们浏览了文件,这些文件的描述包含在服务工作器内置的清单中,加载每个文件并将它们放在相应的缓存块中。 使用新的缓存 API来组织文件存储,这是Service Worker规范的一部分。 该API使用Request对象作为键存储Response对象。 结果,事实证明该存储非常简单。 它与服务工作者事件如何接收请求和返回响应配合得很好。

缓存块的密钥是根据解决方案的部署时间分配的。 时间戳嵌入到HTML代码中,因此可以将其作为文件名的一部分发送到下载每个资源的请求中。 为了避免共享不兼容的资源,将每个部署中的资源分开缓存很重要。 因此,我们可以确保最初下载的HTML文件将仅下载兼容的资源,无论是通过网络下载还是从缓存中下载,都是如此。

▍事件获取


注册服务工作者后,它将开始处理属于同一源的所有网络请求。 开发人员无法这样做,以便某些请求由服务工作者处理,而其他请求则不能。 但是开发人员可以完全控制服务人员收到的请求到底需要做什么。

在处理请求时,我们首先对其进行检查。 如果所请求的内容存在于清单中并且位于缓存中,则我们通过从缓存中获取数据来将响应返回给请求。 如果缓存没有您需要的缓存,我们将返回一个实际的网络请求,该请求将访问实际的网络资源,就像服务工作者完全没有参与此过程一样。 这是fetch事件处理程序的简化版本:

 self.addEventListener('fetch', (e) => {  if (assetManifest.includes(e.request.url) {    e.respondWith(      caches        .open(cacheKey)        .then(cache => cache.match(e.request))        .then(response => {          if (response) return response;          return fetch(e.request);        });    );  } else {    e.respondWith(fetch(e.request));  } }); 

实际上,此类代码包含更多Slack特定的逻辑,但我们的处理程序的核心与本示例一样简单。


在分析网络交互时,可以通过指示数据量的列中的ServiceWorker标记来识别服务工作者返回的响应

▍事件激活


成功安装新的或更新的服务工作者后,将引发activate事件。 我们使用它来分析缓存的资源并使超过7天的缓存块无效。 这是按顺序维护系统的一种很好的做法,此外,它还允许您确保在加载客户端时不会使用太旧的资源。

客户端代码落后于最新版本


您可能已经注意到,我们的实现意味着在客户端的第一次启动之后启动Slack客户端的任何人都不会收到在服务工作者的先前注册期间加载的最新资源,而是缓存的资源。 在原始的客户端实现中,我们尝试在每次下载后更新服务工作者。 但是,典型的Slack用户例如每天早上只能下载一次程序。 这可能导致一个事实,即他将不断与一整天的代码滞后于最新版本的客户合作(我们每天发布几次新版本)。

与典型的网站(访问后很快就会离开)不同,用户计算机上的Slack客户端会打开数小时并处于打开状态。 因此,我们的代码使用寿命很长,这要求我们使用特殊的方法来保持其相关性。

同时,我们努力确保用户使用最新版本的代码,以便他们获得最新功能,错误修复和性能改进。 发布新客户端后不久,我们在其中实现了一种机制,使我们能够缩小用户正在使用的内容与发布的内容之间的差距。 如果在上次更新后部署了新版本的系统,我们将加载新的资源,这些资源将在客户端下次启动时使用。 如果找不到新内容,则不会加载任何内容。 对客户端进行此更改之后,客户端加载资源的平均生存时间减半。


定期下载新版本的代码,但是在下载程序时,仅使用最新版本

新功能标记同步


借助新功能的标志(功能标志),我们在代码库中标记了尚未完成的工作。 这使我们可以在公共发行版之前将新功能包含在代码中。 这种方法降低了生产错误的风险,因为可以在完成新功能之前很长时间就可以与应用程序的其余部分一起免费测试新功能。

当Slack中的新功能对相应的API进行更改时,通常会发布它们。 在开始使用服务工作者之前,我们要确保API的新功能和更改将始终保持同步。 但是,在我们开始使用可能不包含最新版本代码的缓存之后,事实证明,客户端可能处于代码与后端功能不同步的情况。 为了解决这个问题,我们不仅缓存资源,还缓存了一些API响应。

服务人员可以处理所有网络请求,这一事实简化了解决方案。 随着服务工作者的每次更新,我们除其他外执行API请求,将响应与相应资源缓存在同一缓存块中。 这将功能和实验功能与正确的资源连接在一起-可能已过时,但可以保证彼此一致。

实际上,这只是服务人员为开发人员提供的众多机遇的冰山一角。 使用AppCache机制无法解决的问题,或者需要同时解决客户端和服务器机制的问题,可以通过服务工作者和Cache API轻松自然地解决。

总结


服务人员通过组织本地资源存储来加速Slack客户端的加载,这些资源可在客户端下次启动时使用。 网络-我们的用户可能会遇到的延迟和歧义的主要来源,现在对情况几乎没有影响。 可以这么说,我们从等式中删除了它。 而且,如果您可以从等式中删除网络,那么事实证明您可以在项目中实现离线功能。 目前,我们对离线模式的支持非常简单。 用户可以下载客户端,并可以从下载的对话中读取消息。 系统同时准备读取消息上的同步标记。 但是现在,我们为将来实施更高级的机制奠定了基础。

经过几个月的开发,试验和优化,我们了解了很多有关服务人员如何在实践中工作的知识。 此外,事实证明,该技术非常适合大型项目。 在与服务人员公开发布客户的不到一个月的时间内,我们每天成功地为数百万已安装的服务人员提供数千万个请求。 与旧客户相比,这使新客户的加载时间减少了约50%,并且热加载的速度比冷加载的速度快了约25%。


从左到右:加载旧客户端,冷加载新客户端,热加载新客户端(指标越低越好)

亲爱的读者们! 您在项目中使用服务人员吗?


Source: https://habr.com/ru/post/zh-CN468409/


All Articles