前端常见缓存机制

1,401 阅读7分钟

前端常见缓存机制

这是我参与「第四届青训营 」笔记创作活动的的第2天
优秀的缓存机制可以缩短网页请求资源的事件,减少延迟,并且由于缓存文件可以重复利用还可以减少带宽,降低网络负荷。对于一个网络数据资源请求来说,可以分为网络请求、后端服务器响应、浏览器响应三个步骤。浏览器的缓存机制可以帮我们在第一个和第三个步骤中优化性能。比如图片资源可以直接使用缓存二不需要再次发起请求,或者发起了请求后后端服务器发现资源未被修改,于是不需要在响应中传回数据,这样就减小了http报文长度。

接下来我们就来探讨浏览器缓存机制。

数据缓存位置

打开一个网页,检查网络请求部分可以看到有些资源会提示在缓存中拿取。

rtwUJ7BGltzeMH0lPyRvz3hn8YwjIO3yYbR33JZCunY.png

内存缓存(Memory cache)

内存缓存中主要包含的是当前页面中已经抓取到的资源,例如页面中已经下载的css样式、js脚本、图片等。读取内存中的数据比读取磁盘中的数据要快,在上图中,从磁盘缓存中读取的数据为2ms,而在内存中读取的缓存小于1ms。虽然内存缓存读取速度快,但是使用这种缓存方式存储的数据持续性很短,会随着网页进程的释放而释放,比如我们现在关闭当前页面,新开一个页面再次访问该网站:

l05VjdTnOo_yvD1kESCX8tlIVAS29a-ENbDBrAuaEhE.png

发现同样的资源显示是从disk cache(磁盘缓存)中拿取,同时用时也到了2ms。这样设计的原因主要是内存大小通常比磁盘大小要小得多,所以内存资源需要严格控制。

磁盘缓存(disk cache)

大部分的可缓存资源都是缓存在磁盘中的,相比较内存缓存的形式,这种方式读取速度慢一点,但是由于量大且不会因为关闭浏览器抹除数据的原因而大量使用。磁盘缓存会根据 HTTP header头部中的字段判断哪些资源需要缓存,哪些资源可以不需要请求直接使用,哪些资源是已经过期的需要重新获取。关于HTTP协议的缓存策略在下面会进行介绍。

数据缓存过程

通常浏览器缓存策略分为强缓存和协商缓存,这两种缓存策略都和HTTP的请求响应有关。先看两张图:

yq2dr3RMjWDzz-ZJx4T1ql4fY5JO8nF_Z83T-YMYphM.png

twdUOKjBGQoaTKTfYOpWStB7HixEkXNDBXx8cqZDgEY.png

在上图我们可以看到:

  1. 浏览器每次发起请求之前,都会现在浏览器缓存中查找目标资源(包括内存缓存和磁盘缓存)。
  2. 当HTTP响应中有可缓存标识字段时按照规则将资源进行缓存。

这两点就是浏览器缓存机制的关键。

强缓存策略

-o8o179sea_yHg1B83Sqx-YDFewGGgHMjNJ790L4MBA.png

如果资源存在强缓存,则浏览器不会发送该资源的请求而是直接使用缓存,并且状态码为200。可以通过两种HTTP的头部字段实现强缓存:Expires和Cache-Control

  1. Expires:缓存过期时间,在HTTP/1.0中定义,参考时间为浏览器本地时间(也就是说如果人为把电脑时间往后调整该缓存就会过期)。
  2. Cache-Control:在HTTP/1.1中定义,其优先级高于Expires,定义的是可缓存时间比如max-age:300,则在浏览器收到该响应5min之后缓存过期。

Cache-Control字段要比Expires字段更加准确,当两个字段同时存在与某条响应中浏览器会采取Cache-Control的规则,而Expires更多的意义是为了兼容不支持HTTP/1.1的浏览器使用缓存(如果连HTTP/1.0都不支持的话也用不了缓存机制了)。强缓存的意义是为了减少资源的请求数量,但是有些文件我们无法保证在强缓存有效的时间内不修改,这个时候我们就需要使用协商缓存策略。

协商缓存策略

InVxPHLwGyR0GG8HQ9RDgJVi8SH-2MY65D2WgjKQ--s.png

协商缓存就是资源在强缓存失效(或者没有设置强缓存)后,浏览器携带特定缓存标识(HTTP请求头中的某些字段)向服务器发送请求,由服务器根据缓存标识决定是否使用缓存的过程。如果缓存可用则返回状态码304以及在响应头中设置对应字段,否则返回新的资源以及状态码为200。和强缓存一样,可以通过两对HTTP的头部字段实现协商缓存:Last-Modified<->If-Modified-Since和Etag<->If-None-Match。

  1. Last-Modified和If-Modified-Since:在HTTP/1.0中定义,服务·器在响应头中设置Last-modified字段定义协商缓存;在HTTP请求头中使用If-Modified-Since字段询问服务器该资源是否更新。

JLIxk9JnP4JOBZNiySqBcB7v5b770uWQbha9BZddNfU.png
2. Etag和If-None-Match:在HTTP/1.1中定义,服务器在响应头中设置Etag字段告知浏览器进行协商缓存;浏览器在请求头中设置If-None-Match字段值为Etag的值,服务器根据发送的Etag和本地Etag值是否一致告知资源是否被修改。

MGAXv7QPH_4ERZFktA9rKnlTP1vV4sKblGEATNSUv5k.png

使用Last-Modified进行协商缓存时,header字段如下:

Last-Modified:Fri, 22 Jul 2022 08:23:00 GMT

这个时间是资源在服务器删最后修改的时间,如果在服务器上对该资源进行编辑,即使没有修改任何东西也会造成最后修改时间的变动,这样会导致下一次协商缓存时当成新资源返回。

使用Etag进行协商缓存时,header字段如下:

Etag: "1d21-F7V1OXWepGNL2/19at+bjmIP8eA"

其中值为文件的hash值(或者其他唯一标识符),使用文件的hash值的好处是只有当两份文件完全一致时它们的hash值才相同,这样就解决了Last-Modified因为修改时间而造成的重新响应数据

这两个字段在精确度上面,Etag要比Last-Modified精确得多,通过上面我们可以知道Last-Modified是根据文件最后修改时间而确定的,而且精确到了秒数,除了上述那种情况在使用了负载均衡的服务器上各个服务器生成的last-Modified也可能不一样。

在性能上:Etag由于要使用文件的hash值需要对文件数据进行计算所以要逊色于Last-Modified。

在优先级上:Etag是HTTP/1.1的规范,Last-Modified是HTTP/1.0的规范,所以Etag要优先于Last-Modified。

总结

强制缓存优先于协商缓存进行,如果强制缓存生效则直接使用缓存,这个阶段不会发送请求;如果失效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,如果协商缓存失败则返回200以及新的资源的缓存标识,如果生效则返回304继续使用缓存。这里有一个误区就是认为强缓存和协商缓存是可以自由使用的,然而实际上根据MDN上的描述只有当资源被设置了强缓存标识并过期后,才会主动开始协商缓存。对于普通具有可缓存性质但是没有设置强缓存标识的文件,浏览器会使用一种启发式算法:如果满足条件,则触发启发式缓存。比如一个资源最后修改时间是一年前,则客户端将进行缓存,RFC规范指出建议存储距离时间的10%。这一方法是在cache-Control被广泛支持之前的一种解决方法。