了解前端缓存

分类:前端来源:站内 最近更新:2022-08-18 20:00:37浏览:675留言:0

了解前端缓存

前端/后端缓存

在开发过程中,经常会提到“缓存”一词,不管是前端还是后端,缓存的主要作用就是缩短请求和相应时间,增强用户体验。在理解前后端缓存之前,我们先查看通常的网络请求:

image.png

基本的网络请求就是三个步骤:请求,处理,响应。

在第二步,pedding状态就表示前端在等待服务端处理询问的任务。针对读取的数据,如果有不经常更改的特性,后端经常会把这些数据进行缓存,不去数据库读取,直接从redis或者内存中读取,以实现快速完成响应,缩短响应周期。具体这边不做过多的介绍。可以阅读https://zhuanlan.zhihu.com/p/32434005

前端哪里缓存了

本文主要介绍前端是如何缓存的。上图中,排除第二步,就剩下询问和响应了,分别对应了request和response。

我们在日常请求中会出现下图所示的请求,我们从time请求时长可以看出size为memory cache 和disk cache 请求的时间为比较短,甚至为0

blogfile/editor/202208/1660823479980921575.png

我们再看请求头似乎和缓存相关的配置看不到

WX20220818-195213.png

在看看响应头消息,我们可以看到Cache-control,date,last-modified,ETag,max-age等信息 。

WX20220818-195240.png

通过整理我们可以得到以下知识点:

按缓存位置分类 (memory cache, disk cache, Service Worker,Push Cache 等)

按失效策略分类 (Cache-Control, ETag 等)

我们先从缓存位置分类,逐个了解几个类型

memory cache

memory cache 是内存中的缓存,(与之相对 disk cache 就是硬盘上的缓存)。按照操作系统的常理:先读内存,再读硬盘。几乎所有的网络请求资源都会被浏览器自动加入到 memory cache 中。但是也正因为数量很大但是浏览器占用的内存不能无限扩大这样两个因素,memory cache 注定只能是个“短期存储”。常规情况下,浏览器的 TAB 关闭后该次浏览的 memory cache 便告失效 (为了给其他 TAB 腾出位置)。而如果极端情况下 (例如一个页面的缓存就占用了超级多的内存),那可能在 TAB 没关闭之前,排在前面的缓存就已经失效了。

优点:

  • 读取速度快

缺点:

  • 空键有限,一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。

如何触发:

  1. 因为tab页面关闭就消失,所以新标签页不会有memory cache,只有在当前tab再次刷新页面才生效。

  2. base64解析的图片会直接从memory cache 读取,不管清除缓存记录还是关闭tab标签页。

  3. <link rel="preload"> 指定的预加载资源,也会被放入 memory cache

在匹配缓存时,除了匹配完全相同的 URL 之外,还会比对他们的类型,CORS 中的域名规则等。因此一个作为脚本 (script) 类型被缓存的资源是不能用在图片 (image) 类型的请求中的,即便他们 src 相等也会重新请求。

在从 memory cache 获取缓存内容时,浏览器会忽视例如 max-age=0, no-cache 等头部配置。例如页面上存在几个相同 src 的图片,即便它们可能被设置为不缓存,但依然会从 memory cache 中读取。这是因为 memory cache 只是短期使用,大部分情况生命周期只有一次浏览而已。而 max-age=0 在语义上普遍被解读为“不要在下次浏览时使用”,所以和 memory cache 并不冲突。也就是说,即便你设置了no-cache,当用户不关闭你的网页窗口,你代码更新,当前页面刷新依旧不读取最新代码,所以这个时候要让客户关闭打开,或者当前页面强制刷新。

知识扩展:no-cache、no-store区别

如果你连短期都不能容忍,可以配置nginx

location / {
add_header Cache-Control no-store;
}

no-cache从字面意义上很容易误解为不缓存,但是no-cache代表不缓存过期的资源,缓存会向服务器进行有效处理确认之后处理资源,使用no-cache的目的就是为了防止从缓存中获取过期的资源。

no-store才是真正的不进行缓存。

disk cache

disk cache 也叫 HTTP cache,顾名思义是存储在硬盘上的缓存,因此它是持久存储的,是实际存在于文件系统中的。而且它允许相同的资源在跨会话,甚至跨站点的情况下使用,例如两个站点都使用了同一张图片。

disk cache 会严格根据 HTTP 头信息中的各类字段来判定哪些资源可以缓存,哪些资源不可以缓存;哪些资源是仍然可用的,哪些资源是过时需要重新请求的。当命中缓存之后,浏览器会从硬盘中读取资源,虽然比起从内存中读取慢了一些,但比起网络请求还是快了不少的。绝大部分的缓存都来自 disk cache。

关于 HTTP 的协议头中的缓存字段,我们会在稍后进行详细讨论。

优点:

  • 缓存再硬盘中,容量大

缺点:

  • 读取速度满

  • 如何触发:

  • 根据浏览器请求头

扩展:

  • 浏览器会把哪些文件丢进内存中?哪些丢进硬盘中?

  • 关于这点,网上说法不一,不过以下观点比较靠得住:

  • 对于大文件来说,大概率是不存储在内存中的,反之优先

  • 当前系统内存使用率高的话,文件优先存储进硬盘

Service Worker

Service Worker 能够操作的缓存是有别于浏览器内部的 memory cache 或者 disk cache 的。我们可以从 Chrome 的 F12 中,Application -> Cache Storage 找到这个单独的“小金库”。除了位置不同之外,这个缓存是永久性的,即关闭 TAB 或者浏览器,下次打开依然还在(而 memory cache 不是)。有两种情况会导致这个缓存中的资源被清除:手动调用 API cache.delete(resource) 或者容量超过限制,被浏览器全部清空。

Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。

传输协议必须为 HTTPS

Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。

Service Worker 不常用

Push Cache

Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。

它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂

Push Cache 也不常用

优先级

Service Worker=》Memory Cache》Disk Cache=》向服务器请求

  1. 先去内存看,如果有,直接加载

  2. 如果内存没有,择取硬盘获取,如果有直接加载

  3. 如果硬盘也没有,那么就进行网络请求

  4. 加载到的资源缓存到硬盘和内存

访问图片为例:

访问-> 200 -> 退出浏览器->再进来-> 200(from disk cache) -> 刷新 -> 200(from memory cache)

刚刚我们通过位置了解了几个类型,我们在通过按失效策略分类了解缓存机制。在失效策略分类 中,又分强制缓存和协商缓存。

强制缓存 (也叫强缓存)

不会向服务器发送请求,直接从缓存中读取资源。

在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。

强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。

Expires

这是 HTTP 1.0 的字段,表示缓存到期时间,是一个绝对的时间 (当前时间+缓存时间),如

Expires: Thu, 10 Nov 2017 08:45:11 GMT

在响应消息头中,设置这个字段之后,就可以告诉浏览器,在未过期之前不需要再次请求。

但是,这个字段设置时有两个缺点:

  1. 由于是绝对时间,用户可能会将客户端本地的时间进行修改,而导致浏览器判断缓存失效,重新请求该资源。此外,即使不考虑自行修改,时差或者误差等因素也可能造成客户端与服务端的时间不一致,致使缓存失效。

  2. 写法太复杂了。表示时间的字符串多个空格,少个字母,都会导致非法属性从而设置失效。

  3. 如果max-age和Expires同时存在,则被Cache-Control的max-age覆盖

Cache-control

已知Expires的缺点之后,在HTTP/1.1中,增加了一个字段Cache-control,该字段表示资源缓存的最大有效时间,在该时间内,客户端不需要向服务器发送请求。设置如下:

Cache-control: max-age=2592000

Cache-control值如下配置:

  • max-age:即最大有效时间,在上面的例子中我们可以看到

  • must-revalidate:如果超过了 max-age 的时间,浏览器必须向服务器发送请求,验证资源是否还有效。

  • no-cache:虽然字面意思是“不要缓存”,但实际上还是要求客户端缓存内容的,只是是否使用这个内容由后续的对比来决定。

  • no-store: 真正意义上的“不要缓存”。所有内容都不走缓存,包括强制和对比。

  • public:所有的内容都可以被缓存 (包括客户端和代理服务器, 如 CDN)

  • private:所有的内容只有客户端才可以缓存,代理服务器不能缓存。默认值。

扩展:

max-age是HTTP/1.1中,他是指我们的web中的文件被用户访问(请求)后的存活时间,是个相对的值,相对Request_time(请求时间).比如我们发布18:00发布。我们Expires和max-age设置过期时间都是10分钟种后,我们在18:05分钟请求一下页面,那么18:11分刷新时,只有Expires页面被更新,max-age设置的需要18:15以后才会更新。

max-age=0 和 no-cache 等价吗?从规范的字面意思来说,max-age 到期是应该重新验证,而 no-cache 是 必须重新验证。但实际情况以浏览器实现为准,大部分情况他们俩的行为还是一致的。(如果是 max-age=0, must-revalidate 就和 no-cache 等价了)。

总结一下,自从 HTTP/1.1 开始,Expires 逐渐被 Cache-control 取代。Cache-control 是一个相对时间,即使客户端时间发生改变,相对时间也不会随之改变,这样可以保持服务器和客户端的时间一致性。而且 Cache-control 的可配置性比较强大。

Cache-control 的优先级高于 Expires,为了兼容 HTTP/1.0 和 HTTP/1.1,实际项目中两个字段我们都会设置。

对比缓存 (也叫协商缓存)

当强制缓存失效(超过规定时间)时,就需要使用对比缓存,由服务器决定缓存内容是否失效。

流程上说,浏览器先请求缓存数据库,返回一个缓存标识。之后浏览器拿这个标识和服务器通讯。如果缓存未失效,则返回 HTTP 状态码 304 表示继续使用,于是客户端继续使用缓存;如果失效,则返回新的数据和缓存规则,浏览器响应数据后,再把规则写入到缓存数据库。

对比缓存在请求数上和没有缓存是一致的,但如果是 304 的话,返回的仅仅是一个状态码而已,并没有实际的文件内容,因此 在响应体体积上的节省是它的优化点。它的优化覆盖了文章开头提到过的请求数据的三个步骤中的最后一个:“响应”。通过减少响应体体积,来缩短网络传输时间。所以和强制缓存相比提升幅度较小,但总比没有缓存好。

对比缓存是可以和强制缓存一起使用的,作为在强制缓存失效后的一种后备方案。实际项目中他们也的确经常一同出现。

对比缓存有 2 组字段(不是两个):

Last-Modified & If-Modified-Since
  1. 服务器通过 Last-Modified 字段告知客户端,资源最后一次被修改的时间,例如

Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT
  1. 浏览器将这个值和内容一起记录在缓存数据库中。

  2. 下一次请求相同资源时时,浏览器从自己的缓存中找出“不确定是否过期的”缓存。因此在请求头中将上次的 Last-Modified 的值写入到请求头的 If-Modified-Since 字段

  3. 4.服务器会将 If-Modified-Since 的值与 Last-Modified 字段进行对比。如果相等,则表示未修改,响应 304;反之,则表示修改了,响应 200 状态码,并返回数据。

但是他还是有一定缺陷的:

如果资源更新的速度是秒以下单位,那么该缓存是不能被使用的,因为它的时间单位最低是秒。

如果文件是通过服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能没有变化,所以起不到缓存的作用。

Etag & If-None-Match

为了解决上述问题,出现了一组新的字段 Etag 和 If-None-Match

Etag 存储的是文件的特殊标识(一般都是 hash 生成的),服务器存储着文件的 Etag 字段。之后的流程和 Last-Modified 一致,只是 Last-Modified 字段和它所表示的更新时间改变成了 Etag 字段和它所表示的文件 hash,把 If-Modified-Since 变成了 If-None-Match。服务器同样进行比较,命中返回 304, 不命中返回新资源和 200。

Etag 的优先级高于 Last-Modified

ETag是HTTP/1.1标准开始引入的,它是对Last-Modified的补充,主要原因有以下几点:

  • ·一些周期性修改的文件,修改时间变了但内容没变,此时不希望重新GET;

  • ·一些文件修改非常频繁,比如1秒内修改了多次,Last-Modified只能精确到秒;

  • ·一些服务器不能得到文件修改的精确时间;

缓存流程

当浏览器要请求资源时

  1. 调用 Service Worker 的 fetch 事件响应

  2. 查看 memory cache

  3. 查看 disk cache。这里又细分:

a.如果有强制缓存且未失效,则使用强制缓存,不请求服务器。这时的状态码全部是 200

b.如果有强制缓存但已失效,使用对比缓存,比较后确定 304 还是 200

  1. 发送网络请求,等待网络响应

  2. 把响应内容存入 disk cache (如果 HTTP 头信息配置可以存的话)

  3. 把响应内容 的引用 存入 memory cache (无视 HTTP 头信息的配置)

  4. 把响应内容存入 Service Worker 的 Cache Storage (如果 Service Worker 的脚本调用了 cache.put())

总结实例分析并回答问题

  1. memory cache 关闭tab标签存在吗?

  2. 再次打开走什么缓存

  3. 什么时候是304

  4. no-cache & no-store有啥区别

  5. html 头部的meta取消缓存属性 管用吗

<meta http-equiv="Cache-control" content="no-cache" />
<meta http-equiv="Cache" content="no-cache" />

更多文章参考:

https://zhuanlan.zhihu.com/p/44789005

https://blog.csdn.net/weixin_43972437/article/details/105513486


0

发表评论

评论列表(0)

  • 暂时没有留言
热门