在服务工件缓存层和 HTTP 缓存层中使用一致或不同的过期逻辑的利弊。
虽然服务工件和 PWA 正在成为现代 Web 应用的标准,但资源缓存变得比以往更加复杂。本文将介绍浏览器缓存的整体概况,包括:
- 服务工件缓存和 HTTP 缓存的用例和区别。
- 与常规 HTTP 缓存策略相比,不同服务工件缓存过期策略的优缺点。
缓存流程概览
概括来讲,浏览器在请求资源时会遵循以下缓存顺序:
- 服务工件缓存:服务工件会检查资源是否在其缓存中,并根据其编程缓存策略决定是否返回资源本身。请注意,这不会自动发生。您需要在服务工件中创建一个提取事件处理脚本并拦截网络请求,以便从服务工件的缓存(而非网络)提供请求。
- HTTP 缓存(也称为浏览器缓存):如果在 HTTP 缓存中找到资源且该资源尚未过期,浏览器会自动使用 HTTP 缓存中的资源。
- 服务器端:如果 Service Worker 缓存或 HTTP 缓存中没有找到任何内容,浏览器会转到网络请求资源。如果资源未缓存在 CDN 中,请求必须一直返回到源服务器。
缓存层
Service Worker 缓存
服务工件会拦截网络类型的 HTTP 请求,并使用缓存策略来确定应将哪些资源返回给浏览器。服务工件缓存和 HTTP 缓存具有相同的一般用途,但服务工件缓存提供更多缓存功能,例如精细控制要缓存的内容以及缓存方式。
控制服务工件缓存
服务工件使用事件监听器(通常是 fetch
事件)拦截 HTTP 请求。以下代码段演示了先缓存缓存策略的逻辑。
强烈建议使用 Workbox,以免重复造轮子。例如,您可以使用一行正则表达式代码注册资源网址路径。
import {registerRoute} from 'workbox-routing';
registerRoute(new RegExp('styles/.*\\.css'), callbackHandler);
服务工件缓存策略和用例
下表概述了常见的服务工件缓存策略以及每种策略的适用场景。
策略 | 新鲜度理由 | 用例 |
---|---|---|
仅限网络 | 内容必须始终保持最新状态。 |
|
网络回退到缓存 | 最好提供新内容。不过,如果网络故障或不稳定,则可以提供稍旧的内容。 |
|
Stale-while-revalidate | 您可以立即提供缓存的内容,但今后应使用更新后的缓存内容。 |
|
先缓存,然后回退到网络 | 此类内容不太重要,可以从缓存中提供,以提升性能,但服务工件应不时检查是否有更新。 |
|
仅缓存 | 内容很少更改。 |
|
服务工件缓存的其他优势
除了对缓存逻辑进行精细控制之外,服务工件缓存还提供以下功能:
- 为您的源分配更多内存和存储空间:浏览器会按来源分配 HTTP 缓存资源。换句话说,如果您有多个子网域,它们都共享相同的 HTTP 缓存。我们无法保证您的源/网域的内容会长时间保留在 HTTP 缓存中。例如,用户可以通过在浏览器的设置界面中手动清理或触发网页上的硬重载来清除缓存。借助服务工件缓存,缓存的内容更有可能保持缓存状态。如需了解详情,请参阅永久性存储。
- 在网络不稳定或离线体验时更灵活:使用 HTTP 缓存时,您只能做出二元选择:缓存资源或不缓存资源。借助服务工件缓存,您可以更轻松地缓解小问题(使用“在重新验证时使用过时数据”策略),提供完整的离线体验(使用“仅缓存”策略),甚至介于两者之间的体验,例如自定义界面,其中页面的某些部分来自服务工件缓存,而某些部分则在适当情况下被排除(使用“设置捕获处理脚本”策略)。
HTTP 缓存
浏览器首次加载网页和相关资源时,会将这些资源存储在其 HTTP 缓存中。除非最终用户明确停用,否则浏览器通常会自动启用 HTTP 缓存。
使用 HTTP 缓存意味着依赖服务器来确定何时缓存资源以及缓存多长时间。
使用 HTTP 响应标头控制 HTTP 缓存过期
当服务器响应浏览器对资源的请求时,服务器会使用 HTTP 响应标头告知浏览器应将资源缓存多长时间。如需了解详情,请参阅响应标头:配置 Web 服务器。
HTTP 缓存策略和用例
HTTP 缓存比 Service Worker 缓存要简单得多,因为 HTTP 缓存只处理基于时间 (TTL) 的资源到期逻辑。如需详细了解 HTTP 缓存策略,请参阅您应使用哪些响应标头值?和摘要。
设计缓存到期逻辑
本部分介绍了在服务工件缓存层和 HTTP 缓存层中使用一致的失效逻辑的优缺点,以及在这些层中使用单独的失效逻辑的优缺点。
以下 Glitch 演示了在不同场景中,服务工件缓存和 HTTP 缓存的运作方式:
所有缓存层的一致到期逻辑
为了说明优缺点,我们将从 3 个方面来分析:长期、中期和短期。
场景 | 长期缓存 | 中期缓存 | 短期缓存 |
---|---|---|---|
Service Worker 缓存策略 | 缓存,回退到网络 | Stale-while-revalidate | 网络回退到缓存 |
服务工件缓存 TTL | 30 天 | 1 天 | 10 分钟 |
HTTP 缓存 max-age | 30 天 | 1 天 | 10 分钟 |
场景:长期缓存(缓存,回退到网络)
- 缓存的资源有效(不超过 30 天)时:服务工作器会立即返回缓存的资源,而无需访问网络。
- 缓存的资源过期(超过 30 天)时:服务工件会访问网络来提取资源。浏览器的 HTTP 缓存中没有资源的副本,因此它会向服务器端请求资源。
缺点:在这种情况下,HTTP 缓存的价值较低,因为当服务工件中的缓存过期时,浏览器始终会将请求传递到服务器端。
场景:中期缓存(Stale-while-revalidate)
- 缓存的资源有效(小于等于 1 天)时:服务工件会立即返回缓存的资源,然后前往网络提取资源。浏览器的 HTTP 缓存中包含该资源的副本,因此会将该副本返回给服务工作器。
- 缓存的资源已过期(超过 1 天):服务工件会立即返回缓存的资源,并转到网络提取资源。浏览器的 HTTP 缓存中没有资源的副本,因此它会转到服务器端提取资源。
缺点:服务工件需要额外的缓存破坏机制来替换 HTTP 缓存,以充分利用“重新验证”步骤。
场景:短期缓存(网络回退到缓存)
- 当缓存的资源有效(小于等于 10 分钟)时:服务工件会访问网络来提取资源。浏览器的 HTTP 缓存中包含资源的副本,因此它会将该副本返回给服务工件,而无需转到服务器端。
- 缓存的资源过期(超过 10 分钟)时:服务工件会立即返回缓存的资源,并转到网络提取资源。浏览器的 HTTP 缓存中没有资源的副本,因此它会转到服务器端提取资源。
缺点:与中期缓存场景类似,服务工件需要额外的缓存破坏逻辑来替换 HTTP 缓存,以便从服务器端提取最新资源。
在所有场景中使用服务工件
在所有情况下,当网络不稳定时,Service Worker 缓存仍可返回缓存的资源。另一方面,当网络不稳定或宕机时,HTTP 缓存不可靠。
服务工件缓存和 HTTP 层的缓存过期逻辑不同
为了说明利弊,我们将再次从长期、中期和短期三个方面来看待。
场景 | 长期缓存 | 中期缓存 | 短期缓存 |
---|---|---|---|
Service Worker 缓存策略 | 缓存,回退到网络 | Stale-while-revalidate | 网络回退到缓存 |
服务工件缓存 TTL | 90 天 | 30 天 | 1 天 |
HTTP 缓存 max-age | 30 天 | 1 天 | 10 分钟 |
场景:长期缓存(缓存,回退到网络)
- 当缓存的资源在 Service Worker 缓存中有效(小于等于 90 天)时:Service Worker 会立即返回缓存的资源。
- 当服务工件缓存中的缓存资源过期(超过 90 天)时:服务工件会访问网络来提取资源。浏览器的 HTTP 缓存中没有资源的副本,因此它会转到服务器端。
Pros and cons:
- 优点:由于服务工件会立即返回缓存的资源,因此用户可以获得即时响应。
- 优点:服务工件可以更精细地控制何时使用其缓存以及何时请求资源的新版本。
- 缺点:需要制定明确的服务工件缓存策略。
场景:中期缓存(Stale-while-revalidate)
- 当缓存的资源在 Service Worker 缓存中有效(小于等于 30 天)时:Service Worker 会立即返回缓存的资源。
- 当缓存的资源在服务工件缓存中过期(超过 30 天)时:服务工件会访问网络以获取资源。浏览器的 HTTP 缓存中没有资源的副本,因此它会转到服务器端。
Pros and cons:
- 优点:由于服务工件会立即返回缓存的资源,因此用户可以获得即时响应。
- 优点:由于“在后台”进行重新验证,服务工件可以确保对给定网址的下一个请求使用来自网络的新响应。
- 缺点:需要制定明确的服务工件缓存策略。
场景:短期缓存(网络回退到缓存)
- 当缓存的资源在服务工件缓存中有效(小于等于 1 天)时:服务工件会访问网络以获取资源。如果 HTTP 缓存中存在资源,浏览器会返回该资源。如果网络连接中断,服务工件会从服务工件缓存中返回资源
- 当服务工件缓存中的缓存资源过期(超过 1 天)时:服务工件会访问网络来提取资源。由于 HTTP 缓存中的缓存版本已过期,浏览器会通过网络提取资源。
Pros and cons:
- 优点:当网络不稳定或宕机时,服务工件会立即返回缓存的资源。
- 缺点:服务工件需要额外的缓存破坏机制来替换 HTTP 缓存并发出“优先使用网络”请求。
总结
鉴于缓存场景组合的复杂性,我们无法设计出涵盖所有情况的一项规则。不过,根据前面部分的发现,在设计缓存策略时,您可以参考以下几点建议:
- 服务工件缓存逻辑无需与 HTTP 缓存过期逻辑保持一致。如果可能,请在服务工件中使用更长的失效逻辑,以便为服务工件授予更多控制权。
- HTTP 缓存仍然发挥着重要作用,但在网络不稳定或宕机时,它并不可靠。
- 重新审视每个资源的缓存策略,确保您的服务工件缓存策略能够发挥作用,且不会与 HTTP 缓存冲突。