缓存
浏览器缓存是一种用于提高网页加载性能和减少网络流量的技术。浏览器通过在本地存储已下载的资源副本,可以减少对服务器的请求,并在用户再次访问相同页面时更快地加载内容
缓存策略是指浏览器在请求资源时,根据资源的缓存标识决定是否使用本地缓存的策略。缓存策略分为强缓存和协商缓存。
使用缓存的 2 个主要原因:
- 🚀 降低延迟:缓存离客户端更近,因此,从缓存请求内容比从源服务器所用时间更少,呈现速度更快,网站就显得更灵敏。
- 降低网络传输:副本被重复使用,大大降低了用户的带宽使用,其实也是一种变相的省钱(如果流量要付费的话),同时保证了带宽请求在一个低水平上,更容易维护了。
当浏览器请求一个页面时,服务器会返回响应头部,其中包含有关该资源如何缓存的信息。 响应头部中的Cache-Control和Expires字段定义了资源的缓存策略。这些字段告诉浏览器应该在本地缓存资源的时间和方式。
强缓存
HTTP 强缓存是一种缓存机制,它允许客户端在不与服务器通信的情况下直接使用缓存的资源。强缓存通过使用 Cache-Control 和 Expires 头部来实现。
这两个机制都属于强缓存,因为它们都允许客户端在一定时间内使用缓存而无需与服务器进行验证。如果缓存的资源仍然有效,客户端会继续使用它,而不会向服务器发出请求。
当客户端发起请求时,如果缓存有效(即在 max-age 或 Expires 规定的时间内),服务器将返回状态码 304(Not Modified),并且不返回实际的资源内容。客户端则可以使用本地缓存。
使用强缓存有助于减少服务器负载和加速页面加载时间,因为客户端可以直接从本地缓存中获取资源而无需与服务器通信。
Cache-Control
在HTTP协议中,Cache-Control是一个用于控制缓存行为的HTTP头部字段。其中的max-age指令用于指定缓存资源的最大有效时间,以秒为单位。这个指令告诉缓存持有者在该时间内可以重用该资源,而不需要再次从原始服务器请求。
例如,如果服务器返回以下Cache-Control头部:
Cache-Control:public, max-age=3600这表示资源可以被缓存,并且在接下来的3600秒(1小时)内,客户端可以直接使用缓存的副本而不必再次向服务器发起请求。
注意一些重要的点:
max-age的值是一个正整数,代表秒数。它定义了资源的最大缓存时间。- 如果同时存在
max-age和Expires头部字段,max-age的优先级更高。max-age是相对于请求时间的时间段,而Expires是一个具体的日期。 - 如果
max-age的值为0,表示缓存已经过期,客户端必须向服务器发送请求验证缓存的有效性(通过使用If-None-Match或If-Modified-Since等头部字段)。 - 如果
max-age没有被指定,那么缓存的持续时间由其他头部字段(如Expires)来决定。
示例中的头部字段仅为演示目的,实际应用中可能会与其他Cache-Control指令一起使用,如public、private、no-cache等,以更精确地控制缓存行为。例如:
对于应用程序中不会经常改变的静态文件,例如图像,CSS 文件和 JavaScript 文件,你通常可以在发送响应头前添加缓存。
Cache-Control: public, max-age=3600这表示资源可以被公共缓存,并且在接下来的1小时内可以直接使用缓存的副本。
Cache-Control 还可以使用一下字段
max-age:最大有效时间must-revalidate:如果超过了 max-age 的时间,浏览器必须向服务器发送请求,验证资源是否还有效no-cache:不使用强制缓存,需要与服务器验证缓存是否新鲜no-store:真正的“不要缓存”。所有内容都不走缓存,包括强制和对比。public:所有的内容都可以被缓存(包括客户端和代理服务器,如 CDN)private:所有的内容只有客户端才可以缓存,代理服务器不能缓存。默认值。
要注意的就是 no-cache 和 no-store 的区别,no-cache 是跳过强缓存,还是会走协商缓存的步骤,而 no-store 是真正的完全不走缓存,所有资源都不会缓存在本地 该字段可以在请求头或响应头设置。
Expires
Expires 头部指定了资源的过期时间,是一个具体的日期和时间。例如:
Expires: Tue, 01 Jan 2025 12:00:00 GMT在这个过期时间之前,客户端可以直接使用缓存的资源。
Expires 是HTTP头部中的一个字段,用于指定资源的过期时间。它告诉客户端在过了这个过期时间之后,必须重新从服务器请求资源,以确保使用的是最新的版本。与 Cache-Control 头部中的 max-age 指令不同,Expires 使用的是一个具体的日期和时间。
以下是一个示例:
Expires: Tue, 01 Jan 2025 12:00:00 GMT上述例子中,Expires 头部告诉客户端,资源将在 2025 年 1 月 1 日 12:00:00 (GMT) 之后过期。在这个过期时间之前,客户端可以直接使用缓存的资源而不必向服务器发起新的请求。
一些要点:
Expires的时间是服务器时间,而不是本地时间。- 如果同时存在
Cache-Control头部和Expires头部,Cache-Control的指令会覆盖Expires。 - 如果
Expires的值是在过去的时间点,那么相当于资源已经过期,客户端应该重新获取资源。 - 与
max-age不同,Expires不提供相对时间(例如多少秒后过期),而是提供一个具体的过期日期和时间。
然而,Cache-Control 中的 max-age 更加灵活,更容易控制缓存策略。在现代的Web开发中,通常更推荐使用 Cache-Control 来控制缓存行为,因为它提供更多的选项和更精细的控制。
协商缓存
协商缓存是另一种缓存机制,与强缓存相对。协商缓存允许客户端在缓存过期时向服务器发起请求,但服务器会检查资源是否发生了变化。如果资源未发生变化,服务器返回一个告诉客户端可以使用缓存的响应;如果资源发生了变化,服务器返回新的资源给客户端。
协商缓存通常涉及两个HTTP头部:Last-Modified 和 ETag。
Last-Modified/If-Modified-Since
当客户端首次请求资源时,服务器会在响应头部添加 Last-Modified 头部,表示资源的最后修改时间。例如:
Last-Modified: Wed, 01 Jan 2020 12:00:00 GMT客户端在后续请求中可以使用 If-Modified-Since 头部,将上次收到的 Last-Modified 的值发送给服务器。如果资源在这个时间之后没有发生变化,服务器会返回 304 Not Modified,并告诉客户端可以使用缓存。
Etag/If-None-Match
ETag 是一个由服务器生成的唯一标识符,表示资源的状态。服务器在响应头部中添加 ETag 头部,例如:
ETag: "abcdef"客户端在后续请求中可以使用 If-None-Match 头部,将上次收到的 ETag 的值发送给服务器。如果资源的 ETag 没有变化,服务器会返回 304 Not Modified,并告诉客户端可以使用缓存。
如果在 Cache-Control 响应头设置了 "max-age" 或者 "s-max-age" 指令,那么 Expires 头会被忽略。
协商缓存相比于强缓存更加灵活,因为它允许服务器在缓存过期时进行资源验证,确保客户端获得的是最新的版本。在一些情况下,协商缓存可能更适合处理动态内容或者不容易根据时间判断变化的情况。
禁止缓存
禁用HTTP缓存是一种在服务器响应中设置头部,告诉客户端不要缓存资源的方法。这可以确保客户端每次都从服务器获取最新的资源,而不使用任何本地缓存。
有几个头部字段可以用来达到这个目的:
- Cache-Control: no-store:
no-store 是一个Cache-Control的指令,它直接告诉客户端不要存储任何有关于请求/响应的缓存。每次都要从服务器获取最新的资源。示例:
Cache-Control: no-store- Cache-Control: no-cache:
no-cache 意味着客户端必须与服务器验证资源的有效性,但不使用本地缓存。服务器可能返回 304 Not Modified 表示资源未发生变化,但客户端仍然需要发送请求以获取验证。示例:
Cache-Control: no-cache- Cache-Control: max-age=0, must-revalidate:
效果类似于 Cache-Control: no-cache
Cache-Control: max-age=0, must-revalidate- Pragma: no-cache:
虽然 Pragma 是一个较老的头部字段,但仍然被一些客户端支持。Pragma: no-cache 的效果类似于 Cache-Control: no-cache。示例:
Pragma: no-cache使用这些头部字段中的任意一个,或者结合使用,可以禁用HTTP缓存,确保每次都从服务器获取最新的资源。
Cache-control: private, no-cache, no-store, must-revalidate, max-age=0请注意,这样的设置可能会增加服务器负载,因为每个请求都需要在服务器上进行处理,而不是从本地缓存获取资源。
刷新操作
| 类型 | 操作 | 缓存 |
|---|---|---|
| 正常操作 | 地址栏输入 URL,跳转连接,前进后退等 | 强制缓存有效,协商缓存有效 |
| 手动刷新 | F5,点击刷新按钮、右键菜单刷新 | 强制缓存失效,协商缓存有效 |
| 强制刷新 | control + F5 | 强制缓存失效,协商缓存失效 |
浏览器每次发起请求时,先在本地缓存中查找结果以及缓存标识,根据缓存标识来判断是否使用本地缓存。如果缓存有效,则使用本地缓存;否则,向服务器发起请求并携带缓存标识。根据是否需向服务器发起 HTTP 请求,将缓存过程划分为两部分:强制缓存和协商缓存,强缓存优先于协商缓存。 HTTP 缓存都是从第二次请求开始的。
- 第一次请求资源时,服务器返回资源,并且在
response header中回传资源的缓存策略 - 第二次请求时,浏览器判断这些请求参数,击中强缓存就直接 200,否则把请求参数加到
request header中传给服务器,看是否击中了协商缓存,击中则返回304,否则服务器会返回新的资源。
如果响应包含s-maxage指令,那么对于共享缓存(而不是私有缓存),该指令指定的最大年龄将覆盖max-age指令或expires头指定的最大年龄。
缓存类型
浏览器缓存机制有四个方面,它们按照获取资源时请求的优先级依次排列如下:
- Service Worker Cache (from ServiceWorker)
- Memory Cache (from memory cache)
- Disk Cache
- Push Cache(是 HTTP2 的新特性)
Service Worker Cache
Service Worker 是一种独立于主线程之外的 JavaScript 线程。它脱离于浏览器窗体,因此无法直接访问 DOM。这样独立的个性使得 Service Worker 的“个人行为”无法干扰页面的性能,这个“幕后工作者”可以帮我们实现离线缓存、消息推送和网络代理等功能。我们借助 Service worker 实现的离线缓存就称为 Service Worker Cache。 Service Worker 的生命周期包括 install、active、working 三个阶段。一旦 Service Worker 被 install,它将始终存在,只会在 active 与 working 之间切换,除非我们主动终止它。这是它可以用来实现离线存储的重要先决条件。 注意 Server Worker 对协议是有要求的,必须以 https 协议为前提
MemoryCache & Disk Cache
MemoryCache,是指存在内存中的缓存。从优先级上来说,它是浏览器最先尝试去命中的一种缓存。从效率上来说,它是响应速度最快的一种缓存。 内存缓存是快的,也是“短命”的。它和渲染进程“生死相依”,当进程结束后,也就是 tab 关闭以后,内存里的数据也将不复存在。
Disk Cache 就是存储在磁盘中的缓存,从存取效率上讲是比内存缓存慢的,但是它的优势在于存储容量和存储时长。那浏览器如何决定将资源放进内存还是硬盘呢? 从日常开发中,可以总结出这样的规律:资源存不存内存,浏览器秉承的是“节约原则”。
- Base64 格式的图片,几乎永远可以被塞进 memory cache,这可以视作浏览器为节省渲染开销的“自保行为”
- 体积不大的 JS、CSS 文件,也有较大地被写入内存的几率——相比之下,较大的 JS、CSS 文件就没有这个待遇了,内存资源是有限的,它们往往被直接甩进磁盘。内存使用率比较高的时候,文件优先进入磁盘。
Push Cache
Push Cache 是指 HTTP2 在 server push 阶段存在的缓存。推送缓存,是一种低级的网络功能-使用网络堆栈的任何东西都可以使用它,有用的关键是一致性和可预测性,这是浏览器缓存的最后一道防线,它是 http/2 中的内容,虽然现在应用的并不广泛,但随着 http/2 的推广,它的应用越来越广泛。
Push Cache是缓存的最后一道防线。浏览器只有在 Memory Cache、HTTP Cache 和 Service Worker Cache 均未命中的情况下才会去询问Push Cache。Push Cache是一种存在于会话阶段的缓存,当 session 终止时,缓存也随之释放。- 不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个
Push Cache。
总结
- 首先强缓存可用,直接使用
- 否则进入协商缓存,即发送 HTTP 请求,服务器通过请求头中的
If-Modified-Since或者If-None-Match字段检查资源是否更新 - 若资源更新,返回资源和
200状态码 - 否则返回
304,告诉浏览器直接从缓存获取资源