NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

Slack 的 Service Worker 实践:更快的启动速度与离线支持

  • 2019-10-17
  • 本文字数:3457 字

    阅读完需:约 11 分钟

Slack的Service Worker实践:更快的启动速度与离线支持

Service Worker 是网络请求的强大代理,它允许开发人员用少量 JavaScript 控制浏览器响应各个 HTTP 请求。本文将主要讲述 Slack 的 Service Worker 实践,以实现更快的启动速度与离线支持。


我们最近更新了 Slack 的桌面版,新版最大的改进一就是启动速度更快了。在本文中,我们将回顾一下提升 Slack 运行速度,从而改善用户体验的探索工作。


改进方案是从一个名为“快速启动”的原型开始的,该原型旨在(你猜对了)尽快启动 Slack。我们使用了 CDN 缓存的 HTML 文件、持久化的 Redux 存储和一个服务 Worker,从而在不到一秒钟的时间内启动客户端的精简版本(当时,使用 1-2 个工作区的用户普遍需要大约 5 秒钟的启动时间)。Service Worker 是速度提升的最大功臣,它还带来了用户亟需的脱机运行 Slack 的能力。这个原型让我们看到了桌面客户端架构彻底重构后的潜力。基于这种潜力,我们开始基于提升启动速度并引入脱机支持的核心期望重建 Slack 客户端。下面就来深入谈谈背后的工作机制。

什么是 Service Worker?

Service Worker 是网络请求的强大代理,它使开发人员可以用少量 JavaScript 控制浏览器响应各个 HTTP 请求。它们带有丰富而灵活的缓存 API,设计为将请求对象用作键,将响应对象用作值。像 Web Worker 一样,它们独立于单独运行的窗口,在自己的进程中运行。


Service Worker 是现已弃用的应用程序缓存功能的后续工具,后者是为网站提供离线功能的 API。AppCache 的工作机制是提供你要缓存下来供离线使用的静态文件清单……就是这样。它简单但不灵活,无法为开发人员提供控制权。当 W3C 编写 Service Worker 规范时非常重视之前的这些反馈,最后,Service Worker 对你的应用程序或网站进行的所有网络交互都提供了细微的控制权。


当我们第一次涉足这项技术时,支持它的浏览器只有 Chrome 而已,但是我们知道它很快就会广泛普及。现在,所有主要浏览器都开始支持 Service Worker 了。

我们如何使用 Service Worker

当你首次启动新版本的 Slack 时,我们将获取全套资产(HTML、JavaScript、CSS、字体和声音)并将其放置在 Service Worker 的缓存中。我们还将获取你的内存 Redux 存储的副本,并将其推送到 IndexedDB。下次启动时,我们检测这些缓存是否存在。如果存在,我们将使用它们来启动应用程序;如果你在线,我们将在引导后获取新数据。如果不存在,你的客户端也还是可用的。


为了区分这两种路径,我们给它们分别命名为:暖启动和冷启动。通常用户第一次启动是冷启动,没有缓存资产,也没有持久数据。暖启动时我们启动 Slack 所需要的全部数据都存储在用户本地计算机上。请注意,大多数二进制资产(图像、PDF 和视频等)均由浏览器缓存处理(并由普通的缓存头控制)。他们不需要 Service Worker 的显式处理即可离线加载。


Service Worker 生命周期

Service Worker 可以处理三个事件:安装获取激活。每个事件我们都会深入研究,但首先必须下载并注册 Service Worker。生命周期取决于浏览器处理 Service Worker 文件更新的方式。根据 MDN 的 API 文档:


当下载的文件是新文件时尝试安装——所谓新文件既可能是与现有 Service Worker 不同(按字节比较),或者是此页面 / 站点遇到的第一个 Service Worker。


每次我们更新相关的 JavaScript、CSS 或 HTML 文件时,它都会通过自定义的 webpack 插件运行,该插件会生成这些文件的带有唯一哈希值的清单。它被嵌入到 Service Worker 中,即使实现本身未更改也将在下次启动时触发更新。

安装

每当 Service Worker 更新时我们都会收到安装事件。作为响应,我们遍历嵌入式清单中的文件,获取每个文件并将它们放入共享的缓存桶中。文件使用新的缓存 API 存储,这个 API 是 Service Worker 规范的另一部分。它存储以以请求对象为键的响应对象:非常优雅直观,且与 Service Worker 事件接收请求并返回响应的方式完全一致。


我们通过部署时间为缓存桶设置键。时间戳嵌入在我们的 HTML 中,因此可以作为文件名的一部分传递给所有资产请求。分别缓存各个部署中的资产是很重要的,可以预防不匹配现象。通过这种设置,我们可以确保最初获取的 HTML 文件从缓存或网络中只能获取兼容的资产。

获取

注册后,我们的 Service Worker 将被设置为处理来自同一来源的每个网络请求。你无法选择是否让 Service Worker 处理请求,但是你完全可以控制该请求的处理方式。


首先我们检查请求。如果它在清单中并且存在于缓存中,我们将返回已缓存的响应。如果没在清单里,我们将为相同的请求返回一个获取调用,将请求传递到网络,好像不存在 Service Worker 一样。以下是我们获取处理程序的简化版本:


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 特定的逻辑,但获取处理程序的核心就是这么简单。



从 Service Worker 返回的响应将在网络检查器的大小列中标记为“(ServiceWorker)”

激活

成功安装新的 Service Worker 或更新 Service Worker 后,会触发 activate 事件。我们用它来回顾缓存资产,并让所有超过 7 天的缓存桶失效。它不仅能打扫好房间,也可以防止客户端使用过时的资产启动。

落后一个版本

你可能已经注意到,我们的实现意味着在第一次启动 Slack 客户端后,所有人都将收到上次注册 Service Worker 时获取的资产,而不是启动时最新部署的资产。我们的初始实现尝试在每次引导后更新 Service Worker。但是,典型的 Slack 客户可能只在每天早晨启动一次,并且可能会发现自己整整一天都在用落后一个版本的应用(我们每天更新多次代码)。


与你快速浏览的典型网站不一样的是,Slack 在人们工作时会在他们的计算机上停留数小时之久。这使我们的代码具有较长的保存期限,并且需要一些不一样的方法来保持最新状态。


我们仍然希望用户使用最新的版本,以便获得最新的错误修复、性能改进和功能。发布新版客户端后不久,我们就在抖动的间隔中添加了注册过程以缩小间隔。如果自上次更新以来有新版本部署,我们将获取新资产以备下次启动。如果没有,注册机制什么都不会做。做出这项更改后,客户端启动资产的平均过时时间减少了一半。



定期获取新版本,但仅使用最新版本启动

功能标记同步

功能标记是我们代码库中的条件,可让我们合并未完成的工作以准备公开发布。有了它,我们在完成功能之前很长时间就可以与应用程序的剩余部分一起自由测试功能,从而降低了风险。


Slack 的日常工作流程是发布新功能以及我们 API 中的相应更改。在引入 Service Worker 之前我们已经保证两者是同步的,但现在我们的缓存资产落后了一个版本,客户端很可能就和后端不同步了。为了解决这个问题,我们不仅要缓存 资产,还会缓存 一些 API 响应。


Service Worker 处理每个网络请求的能力简化了解决方案。每次 Service Worker 更新时,我们也会发出 API 请求,并将响应缓存在与资产相同的存储桶中。这会将我们的功能和实验与正确的资产相关联——可能它们都过时了,但一定要保持同步。


这是 Service Worker 可能引发问题的冰山一角。这个问题用 AppCache 要么完全没法解决,要么需要非常复杂的技术栈;但是有了 Service Worker 和缓存 API,解决方案就变得非常简单且顺其自然了。

总结

Service Worker 将 Slack 的资产存储在本地以备下次启动时使用,从而加快了启动速度。我们最大的延迟和可变性来源就是网络。而且如果你可以将网络排除在外,那么你也很容易提供离线支持。现在我们对脱机功能的支持非常简单——你可以启动 Slack,从以前阅读过的对话中读取消息,并将未读标记设置为在线后再同步——但有了这个舞台,我们将来就能开发更高级的离线功能了。


经过几个月的开发、试验和优化,我们已经学到了很多有关 Service Worker 如何在实践中操作的知识。而且该技术已经得到了大规模应用的实证:在公开发布后不到一个月的时间里,我们就通过数百万已安装的 Service Worker 成功地为每天数千万的请求提供了服务。与传统客户端相比,这意味着启动时间减少了约 50%;与冷启动相比启动时间缩短了约 25%。



在浏览器中使用 p50 旧版、暖启动和冷启动的测试结果(越低越好)


原文链接:


https://slack.engineering/service-workers-at-slack-our-quest-for-faster-boot-times-and-offline-support-3492cf79c88


2019-10-17 10:211834

评论

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

ABBYY2022全新版PDF文字识别功能

茶色酒

你中奖了吗?低代码开发师(高级)认证中奖名单揭晓啦!

一只大光圈

钉钉宜搭

String基础整合

工程师日月

java 5月月更

先进数据中心背后,“东数西算”的三重意志

脑极体

AIrserver2022手机软件无线投屏电脑屏幕

茶色酒

AirServer

FinClip小程序+Rust(三):一个加密钱包

Speedoooo

rust 前端框架 小程序容器

HIVE3 深度剖析 (下篇)

明哥的IT随笔

大数据 hive

Redis「5」事件处理模型与键过期策略

Samson

学习笔记 Redis 核心技术与实战 5月月更

学生管理系统(5)

5月月更

设计模式之装饰器模式

乌龟哥哥

5月月更

OpenHarmony浏览器上新,在Dayu200开发板上终于能优雅地浏览网页

离北况归

浏览器 OpenHarmony Openharmony啃论文俱乐部 PIMF OpenHarmony应用安装

宠物类自媒体运营心得:如何才能拍得更有创意

石头IT视角

源码分析 Flutter 的 setState 过程

岛上码农

flutter ios 前端 跨平台开发 5月月更

企业架构如何促进创新?

涛哥 数字产品和业务架构

企业架构

数据库连接池 -Druid 源码学习(六)

wjchenge

Druid 数据库连接池

FinClip小程序+Rust(二):环境搭建

Speedoooo

rust 前端框架 小程序容器

服务网格接口 SMI 规范解读

Flomesh

云原生 服务网格 SMI OpenServiceMesh

HDD·耀星领航出海峰会:华为游戏中心联运服务加速游戏出海获量增长

最新动态

FinClip小程序+Rust(一):夹心饼架构

Speedoooo

rust 前端框架 小程序容器

大家都在用哪些OKR管理工具?

PingCode

OpenMLDB v0.5.0 发布 | 性能、成本、灵活性再攀高峰!

第四范式开发者社区

人工智能 机器学习 数据库 数据 特征平台

【JavaScript】数值转换为数值

恒山其若陋兮

5月月更

Go Web 编程入门:Go pongo2 模板引擎

宇宙之一粟

Go web Go 语言 模板 5月月更

柏拉图会成为元宇宙风险标吗?PlatoFarm的机会很大

小哈区块

JavaWeb 数据库操作

Emperor_LawD

sql javaWeb 5月月更

李俊刚:我是如何在OpenHarmony完成ap6275s WiFi驱动的HDF适配工作的?

OpenHarmony开发者

OpenHarmony WiFi驱动

飞书将于5月25日举行春季发布会 同步推出全新项目管理产品

陈泽涛

飞书 飞书项目

零基础学Java第一节(语法格式、数据类型)

编程攻略

java 5月月更

druid 源码阅读 6——如何实现断链重连的?

张大彪

druid 源码阅读(七)Druid Filter 介绍

爱晒太阳的大白

5月月更

三种常见的 Mac 安装 git 工具的方法

liuzhen007

git git 学习 5月月更

Slack的Service Worker实践:更快的启动速度与离线支持_大前端_Slack Engineering_InfoQ精选文章