首页 > 技术文章 > 前端缓存机制-概念理解篇

jiajia-hjj 2021-08-30 21:10 原文

前端缓存机制-概念理解篇

一、前言

前端缓存

  • HTTP缓存:在HTTP请求传输时用到的缓存,主要在服务器代码上设置

  • 浏览器缓存:浏览器缓存则主要由前端开发在前端js上进行设置

缓存的作用

  • 性能优化,缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。

  • 数据请求分为:网络请求后端处理浏览器响应三个步骤。

  • 浏览器缓存可以在第一和第三步骤中优化性能。比如,直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。

二、缓存过程分析

  • 浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求。
  • 那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中HTTP头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中,简单的过程如下图:

由上图我们可以知道:

  • 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识

  • 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中

三 、强制缓存

强制缓存:向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种,如下:

①不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求(跟第一次发起请求一致),如下图:

②存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存,如下图

③存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果,如下图

那么强制缓存的缓存规则是什么?

  • 当浏览器向服务器发起请求时,服务器会将缓存规则放入HTTP响应报文的HTTP头中和请求结果一起返回给浏览器
  • 控制强制缓存的字段分别是:ExpiresCache-Control,其中Cache-Control优先级比Expires高。

3.1.Expires

  • Expires是HTTP/1.0控制网页缓存的字段,其值为服务器返回该请求结果缓存的到期时间,即再次发起该请求时,如果客户端的时间小于Expires的值时,直接使用缓存结果。

  • 到了HTTP/1.1,Expire已经被Cache-Control替代,原因在于Expires控制缓存的原理是使用客户端的时间与服务端返回的时间做对比,那么如果客户端与服务端的时间因为某些原因(例如时区不同;客户端和服务端有一方的时间不准确)发生误差,那么强制缓存则会直接失效,这样的话强制缓存的存在则毫无意义。

3.2.Cache-Control

  • 在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存,主要取值为:

    • public:所有内容都将被缓存(客户端和代理服务器都可缓存)

    • private:所有内容只有客户端可以缓存,Cache-Control的默认取值

    • no-cache:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定

    • no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存

    • max-age=xxx (xxx is numeric):缓存内容将在xxx秒后失效

例子,如下:

由上面的例子我们可以知道:

①HTTP响应报文中expires的时间值,是一个绝对值

②HTTP响应报文中Cache-Control为max-age=600,是相对值

由于Cache-Control的优先级比expires高,那么直接根据Cache-Control的值进行缓存,意思就是说在600秒内再次发起该请求,则会直接使用缓存结果,强制缓存生效。

注:在无法确定客户端的时间是否与服务端的时间同步的情况下,Cache-Control相比于expires是更好的选择,所以同时存在时,只有Cache-Control生效。

思考:浏览器的缓存存放在哪里,如何在浏览器中判断强制缓存是否生效?

状态码为灰色的请求则代表使用了强制缓存,请求对应的Size值则代表该缓存资源存放的位置,分别为memory cache 和 disk cache。

问题:那么memory cache 和 disk cache又分别代表的是什么呢?什么时候会使用disk cache,什么时候会使用memory cache呢?

四、缓存位置

4.1. Service Worker

  • 传输协议必须为 HTTPS。
  • Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
  • Service Worker 实现缓存功能:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。
  • Chrome 的 F12 中,Application -> Cache Storage 。永久性的,即关闭 TAB 或者浏览器,下次打开依然还在。
  • 资源被清除:手动调用 API cache.delete(resource) 或者容量超过限制,被浏览器全部清空。

4.2.memory cache

  • 内存中的缓存
  • 高效,读取内存中的数据肯定比磁盘快;缓存持续性短,会随着进程的释放而释放;关闭 Tab 页面,内存中的缓存也就被释放了。

4.3. Disk Cache

  • 硬盘中的缓存
  • 进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。
- memory cache disk cache
相同点 只能存储一些派生类资源文件 只能存储一些派生类资源文件
不同点 退出进程时数据会被清除 退出进程时数据不会被清除
存储资源 一般脚本、字体、图片会存在内存当中 一般非脚本会存在内存当中,如css等

原因:

  • CSS文件加载一次就可渲染出来,不会频繁读取它,所以它不适合缓存到内存中
  • js之类的脚本,却随时可能会执行,如果脚本在磁盘当中,我们在执行脚本的时候需要从磁盘取到内存中来,这样IO开销就很大了,有可能导致浏览器失去响应。

4.4.Push Cache

  • 推送缓存: HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。

浏览器读取缓存的顺序

  • Service Worker 是否命中缓存,没有调用 fetch 函数,缓存查找。

  • 缓存查找优先级:memory –> disk->push –> 服务器请求。(应该是Service Worker开始。。有点不太懂Service Worke)

页面缓存读取问题分析:

  • 首次访问页面: 服务器请求

  • 关闭博客的标签页,重新打开页面:内存缓存被清除,会读取硬盘的缓存

  • 刷新页面:内存缓存没有被清除,读取内存缓存,读取硬盘缓存

五、协商缓存解决

  • 解决强制缓存下,资源不更新问题。
  • 协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:

①协商缓存生效,返回304,如下

②协商缓存失效,返回200和请求结果结果,如下

  • 协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回给浏览器的,

  • 控制协商缓存的字段分别有:Last-Modified / If-Modified-SinceEtag / If-None-Match,其中Etag / If-None-Match的优先级比Last-Modified / If-Modified-Since高。

5.1.Last-Modified / If-Modified-Since

  • Last-Modified :服务器响应请求时,返回该资源文件在服务器最后被修改的时间,如下。

  • If-Modified-Since:是客户端再次发起该请求时,携带上次请求返回的Last-Modified值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。服务器收到该请求,发现请求头含有If-Modified-Since字段,则会根据If-Modified-Since的字段值与该资源在服务器的最后被修改时间做对比,若服务器的资源最后被修改时间大于If-Modified-Since的字段值,则重新返回资源,状态码为200;否则则返回304,代表资源无更新,可继续使用缓存文件,如下。

  • Last-Modified的不足:
    • 虽然对资源进行编辑,但是内容没有进行任何变化,时间戳也会更新,有效性的判断验证失效,需要重新进行完整资源请求。
    • 修改的时间单位是秒,如果文件修改的速度非常快,假设在几百毫秒,那么上述通过时间戳的方式来验证缓存的有效性,是无法识别出该次文件资源的更新的。
    • 其实造成上述两种缺陷的原因相同,就是服务器无法仅依据资源修改的时间戳来识别出真正的更新,进而导致重新发起了请求,该重新请求却使用了缓存的 Bug 场景。

5.2.Etag / If-None-Match

  • Etag:服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成,如md5),如下。

  • If-None-Match:是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。服务器收到该请求后,发现该请求头中含有If-None-Match,则会根据If-None-Match的字段值与该资源在服务器的Etag值做对比,一致则返回304,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200,如下。

  • Etag的不足:ETag 并非 last-modified 的替代方案而是一种补充方案
    • 服务器对于生成文件资源的 ETag 需要付出额外的计算开销,如果资源的尺寸较大,数量较多且修改比较频繁,那么生成 ETag 的过程就会影响服务器的性能。
    • ETag 字段值的生成分为强验证和弱验证,强验证根据资源内容进行生成,能够保证每个字节都相同。弱验证则根据资源的部分属性值来生成,生成速度快但无法确保每个字节都相同,会因为不够准确而降低协商缓存有效性验证的成功率。

注:Etag / If-None-Match优先级高于Last-Modified / If-Modified-Since,同时存在则只有Etag / If-None-Match生效。对于协商缓存,使用 Ctrl+F5强制刷新可以使得缓存无效。但是对于强缓存,在未过期时,必须更新资源路径才能发起新的请求(更改了路径相当于是另一个资源了,这也是前端工程化中常用到的技巧)。

六、总结

  • 强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存
  • 若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match)
  • 协商缓存由服务器决定是否使用缓存
  • 若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存,主要过程如下:

七、缓存决策

6.1.缓存决策树

img

6.2.缓存决策实例

我们既希望缓存能在客户端尽可能久的保存,又希望它能在资源发生修改时进行及时更新。

  • HTML :包含其他文件的主文件,为保证当其内容发生修改时能及时更新,应当将其设置为协商缓存。
  • 图片文件:因为网站对图片的修改基本都是更换修改,同时考虑到图片文件的数量及大小可能对客户端缓存空间造成不小的开销,所以可采用强制缓存且过期时间不宜过长,故可设置cache-control:max-age=86400
  • 样式表文件style.css:由于其属于文本文件,可能存在内容的不定期修改,又想使用强制缓存来提高重用效率,可以考虑在样式表文件的命名中增加文件指纹或版本号(比如添加文件指纹后的样式表文件名变为了style.51ad84f7.css)。请求的文件 URL 不同了,因此必然会发生对资源的重新请求。同时考虑到网络中浏览器与CDN等中间代理的缓存,其过期时间可适当延长到一年,即cache-control:max-age=31536000
  • js脚本文件:其可类似于样式表文件的设置,采取文件指纹和较长的过期时间。如果js中包含了用户的私人信息而不想让中间代理缓存,则可为cache-control:private

八、浏览器缓存

8.1.本地存储小容量

  • Cookie:主要用于用户信息的存储,Cookie的内容可以自动在请求的时候被传递给服务器。

  • LocalStorage:它的数据将一直保存在浏览器内,直到用户清除浏览器缓存数据为止。

  • SessionStorage:属性LocalStorage一样,但是它生命周期同标签页的生命周期,当标签页被关闭时,SessionStorage也会被清除。

8.2.本地存储大容量

  • WebSqlIndexDB:主要用在前端有大容量存储需求的页面上,例如,在线编辑浏览器或者网页邮箱。

8.3.往返缓存

  • 往返缓存又称为BFCache,是浏览器在前进后退按钮上为了提升历史页面的渲染速度的一种策略。

  • 该策略具体表现为,当用户前往新页面时,将当前页面的浏览器DOM状态保存到BFCache中;当用户点击后退按钮的时候,将页面直接从BFCache中加载,节省了网络请求的时间。

大部分转自作者:Thomas赵骐

原文链接:https://www.jianshu.com/p/256d0873c398

学习资源:

推荐阅读