在线文档教程

生产环境下的 Service Worker

生产环境下的 Service Worker

本页讲的是如何使用 Angular Service Worker 发布和支持生产环境下的应用。 它解释了 Angular Service Worker 如何满足大规模生产环境的需求、Service Worker 在多种条件下有哪些行为以及有哪些可用的资源和故障保护机制。

前提条件

对下列知识有基本的了解:

Service Worker 与应用资源的缓存

从概念上说,你可以把 Angular Service Worker 想象成一个转发式缓存或装在最终用户浏览器中的 CDN 边缘。 Service Worker 的工作是从本地缓存中满足 Angular 应用对资源或数据的请求,而不用等待网络。 和所有缓存一样,它有一些规则来决定内容该如何过期或更新。

应用的版本

在 Angular Service Worker 的语境下,“版本”是指用来表示 Angular 应用的某一次构建成果的一组资源。 当应用的一个新的构建发布时,Service Worker 就把它看做此应用的一个新版本。 就算只修改了一个文件,也同样如此。 在任何一个给定的时间,Service Worker 可能会在它的缓存中拥有此应用的多个版本,这几个版本也都能用于提供服务。 要了解更多,参见稍后的 App 选项卡

要保持应用的整体性,Angular Service Worker 会用所有的文件共同组成一个版本。 组成版本的这些文件通常包括 HTML、JS 和 CSS 文件。把这些文件分成一组是至关重要的,因为它们会互相引用,并且依赖于一些特定内容。 比如,index.html 文件可能有个引用 bundle.js 的 <script>标签,它可能会试图从这个脚本中调用一个 startApp() 函数。 任何时候,只要这个版本的 index.html 被提供了,与它对应的 bundle.js 也必须同时提供。 这种情况下,使用调用了 startApp() 的老的 index.html并同时使用定义了 runApp() 的新 bundle 就是无效的。

当使用惰性加载模块时,文件的整体性就显得格外重要。 某个 JS 包可能引用很多惰性块,而这些惰性块的文件名在应用的每次特定的构建中都是唯一的。 如果运行应用的 X 版本视图加载一个惰性块,但该块的服务器已经升级到了 X + 1版本,这次惰性加载操作就会失败。

本应用的版本标识符由其所有资源的内容决定,如果它们中的任何一个发生了变化,则版本标识符也随之改变。 实际上,版本是由 ngsw.json 文件的内容决定的,包含了所有已知内容的哈希值。 如果任何一个被缓存的文件发生了变化,则该文件的哈希也将在ngsw.json中随之变化,从而导致 Angular Service Worker 将这个活动文件的集合视为一个新版本。

借助 Angular Service Worker 的这种版本控制行为,应用服务器就可以确保这个 Angular 应用中的这组文件始终保持一致。

更新检测

每当用户打开或刷新应用程序时,Angular Service Worker 都会通过查看清单(manifest)文件 “ngsw.json” 的更新来检查该应用程序的更新。 如果它找到了更新,就会自动下载并缓存这个版本,并在下次加载应用程序时提供。

资源整体性

长周期缓存的潜在副作用之一就是可能无意中缓存了无效的资源。 在普通的HTTP缓存中,硬刷新或缓存过期限制了缓存这种无效文件导致的负面影响。 而 Service Worker 会忽略这样的约束,事实上会对整个应用程序进行长期缓存。 因此,让 Service Worker 获得正确的内容就显得至关重要。

为了确保资源的整体性,Angular Service Worker 会验证所有带哈希的资源的哈希值。 通常,对于 CLI 应用程序,用户的 src/ngsw-config.json配置文件中会涵盖 dist 目录下的所有内容。

如果某个特定的文件未能通过验证,Angular Service Worker 就会尝试用 “cache-busting” URL 为参数重新获取内容,以消除浏览器或中间缓存的影响。 如果该内容也未能通过验证,则 Service Worker 会认为该应用的整个版本都无效,并停止用它提供服务。 如有必要,Service Worker 会进入安全模式,这些请求将退化为直接访问网络。 如果服务无效、损坏或内容过期的风险很高,则会选择不使用缓存。

导致哈希值不匹配的原因有很多:

  • 在源服务器和最终用户之间缓存图层可能会提供陈旧的内容。

不带哈希的内容

ngsw.json 清单中唯一带哈希值的资源就是构建清单时 dist 目录中的资源。 而其他资源,特别是从 CDN 加载的资源,其内容在构建时是未知的,或者会比应用程序部署得更频繁。

如果 Angular Service Worker 没有哈希可以验证给定的资源,它仍然会缓存它的内容,但会使用 “重新验证时失效” 的策略来承认HTTP缓存头。 也就是说,当被缓存资源的 HTTP 缓存头指出该资源已过期时,Angular Service Worker 将继续提供内容,并尝试在后台刷新资源。 这样,那些被破坏的非哈希资源留在缓存中的时间就不会超出为它配置的生命周期。

App 选项卡

如果应用程序的资源版本突然发生了变化或没有给出警告,就可能会有问题。有关这些问题的描述,请参阅前面的 版本 部分。

Angular Service Worker 会保证:正在运行的应用程序会继续运行和当前应用相同的版本。 而如果在新的 Web 浏览器选项卡中打开了该应用的另一个实例,则会提供该应用的最新版本。 因此,这个新标签可以和原始标签同时运行不同版本的应用。

值得注意的是,这种担保比普通的 Web 部署模型提供的担保还要更强一点。 如果没有 Service Worker,则不能保证稍后在这个正在运行的应用中惰性加载的代码 和其初始代码的版本是一样的。

Angular Service Worker 为什么可能会更改运行中的应用的版本有几个有限的原因。 其中一些是因为出错了:

  • 由于哈希验证失败,当前版本变成了无效的。

Angular Service Worker 能知道在任何指定的时刻正在使用哪些版本, 并清除那些没有被任何选项卡使用的版本。

另一些可能导致 Angular Service Worker 在运行期间改变版本的因素是一些正常事件:

  • 页面被重新加载/刷新。

Service Worker 更新

Angular Service Worker 是一个运行在Web浏览器中的小脚本。 有时,这个 Service Worker 也可能会需要更新,以修复错误和增强特性。

首次打开应用时或在一段非活动时间之后再访问应用程序时,就会下载 Angular Service Worker。 如果 Service Worker 发生了变化,Service Worker 就会在后台进行更新。

Angular Service Worker 的大部分更新对应用程序来说都是透明的 - 旧缓存仍然有效,其内容仍然能正常使用。 但是,在 Angular Service Worker 中可能偶尔会有错误修复或新功能,需要让旧的缓存失效。 这时,应用程序就从会网络上透明地进行刷新。

调试 Angular Service Worker

偶尔,可能会需要检查运行中的 Angular Service Worker,以调查问题或确保它在按设计运行。 浏览器提供了用于调试 Service Worker 的内置工具,而且 Angular Service Worker 本身也包含了一些有用的调试功能。

定位并分析调试信息

Angular Service Worker 会在虚拟目录 ngsw/ 下暴露出调试信息。 目前,它暴露的唯一的 URL 是 ngsw/state。 下面是这个调试页面中的一段范例内容:

content_copyNGSW Debug Info: Driver state: NORMAL ((nominal))Latest manifest hash: eea7f5f464f90789b621170af5a569d6be077e5cLast update check: never === Version eea7f5f464f90789b621170af5a569d6be077e5c === Clients: 7b79a015-69af-4d3d-9ae6-95ba90c79486, 5bc08295-aaf2-42f3-a4cc-9e4ef9100f65 === Idle Task Queue ===Last update tick: 1s496uLast update run: neverTask queue:  * init post-load (update, cleanup) Debug log:

驱动程序的状态

第一行表示驱动程序的状态:

content_copyDriver state: NORMAL ((nominal))

NORMAL 表示这个 Service Worker 正在正常运行,并且没有处于降级运行的状态。

有两种可能的降级状态:

  • EXISTING_CLIENTS_ONLY:这个 Service Worker 没有该应用的最新已知版本的干净副本。 较旧的缓存版本可以被安全的使用,所以现有的选项卡将继续使用较旧的版本运行本应用, 但新的应用将从网络上加载。

在这两种情况下,后面的括号注解中都会提供导致 Service Worker 进入降级状态的错误信息。

最新清单的哈希

content_copyLatest manifest hash: eea7f5f464f90789b621170af5a569d6be077e5c

这是 Service Worker 所知道的应用最新版本的 SHA1 哈希值。

最后一次更新检查

content_copyLast update check: never

这表示 Service Worker 最后一次检查应用程序的新版本或更新的时间。“never” 表示 Service Worker 从未检查过更新。

在这个调试文件范例中,这次更新检查目前是已排期的,如下一节所述。

版本

content_copy=== Version eea7f5f464f90789b621170af5a569d6be077e5c === Clients: 7b79a015-69af-4d3d-9ae6-95ba90c79486, 5bc08295-aaf2-42f3-a4cc-9e4ef9100f65

在这个例子中,Service Worker 拥有一个版本的应用程序缓存并用它服务于两个不同的选项卡。 请注意,这个版本哈希值是上面列出的“最新清单的哈希”。 它的两个客户运行的都是最新版本。每个客户都用浏览器中 Clients API的 ID 列了出来。

空闲任务队列

content_copy=== Idle Task Queue === Last update tick: 1s496u Last update run: never Task queue: * init post-load (update, cleanup)

空闲任务队列是 Service Worker 中所有在后台发生的未决任务的队列。 如果这个队列中存在任何任务,则列出它们的描述。 在这个例子中,Service Worker 安排的任务是一个用于更新检查和清除过期缓存的后期初始化操作。

最后的 tick/run 计数器给出了与特定事件发生有关的空闲队列中的时间。 “Last update run” 计数器显示的是上次执行空闲任务的时间。 “Last update tick” 显示的是自上次事件以来可能要处理的队列的时间。

调试日志

content_copyDebug log:

在 Service Worker 中出现的任何错误都会记录在这里。

开发者工具

Chrome 等浏览器提供了能与 Service Worker 交互的开发者工具。 这些工具在使用得当时非常强大,但也要牢记一些事情。

  • 使用开发人员工具时,Service Worker 将继续在后台运行,并且不会重新启动。 这可能会导致开着 Dev Tools 时的行为与用户实际遇到的行为不一样。

在 Service Worker 页停止并重新启动这个 Service Worker 将会触发一次更新检查。

Service Worker 的安全性

像任何复杂的系统一样,错误或损坏的配置可能会导致 Angular Service Worker 以不可预知的方式工作。 虽然它在设计时就尝试将此类问题的影响降至最低,但是,如果管理员需要快速停用 Service Worker, Angular Service Worker 也包含多种故障保护机制。

故障保护机制

要停用 Service Worker,请删除或重命名 ngsw-config.json 文件。 当 Service Worker 对 ngsw.json 的请求返回 404 时,Service Worker 就会删除它的所有缓存并注销自己,本质上就是自毁。

Safety Worker

@angular/service-worker NPM 包中还包含一个小脚本safety-worker.js,当它被加载时就会把它自己从浏览器中注销。 这个脚本可以作为终极武器来摆脱那些已经安装在客户端页面上的不想要的 Service Worker。

要特别注意的是,你不能直接注册这个 Safety Worker,因为具有已缓存状态的旧客户端可能无法看到一个新的、用来安装 另一个 worker 脚本的 index.html。 相反,你必须在想要注销的 Service Worker 脚本的 URL 中提供 safety-worker.js 的内容, 而且必须持续这样做,直到确定所有用户都已成功注销了原有的 Worker。 对大多数网站而言,这意味着你应该永远为旧的 Service Worker URL 提供 这个 Safety Worker。

这个脚本可以用来停用 @angular/service-worker 以及任何其它以前在你的站点上提供过的 Service Worker。