理解浏览器缓存策略

495 阅读7分钟

前言

缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。

对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。

缓存位置

  • Service Worker
  • Memory Cache
  • Disk Cache
  • Push Cache

1.Memory Cache

Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。

那么既然内存缓存这么高效,我们是不是能让数据都存放在内存中呢? 这显然是不可能的。计算机中的内存一定比硬盘容量小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。

2.Disk Cache

Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,比之 Memory Cache 胜在容量和存储时效性上。

在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Header 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache。

至于哪些资源会存进内存,哪些资源会存进硬盘。网上的说法不一,不过以下的观点比较靠得住。

  • 对于大文件来说,大概率不存在内存中。
  • 当前系统内存使用率过高情况下,文件优先存进硬盘中。

缓存过程

缓存策略是什么? 浏览器的缓存策略分为两种:强缓存和协商缓存,浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的

强缓存

强缓存:不会向服务器发送请求,直接从缓存中读取资源,强缓存可以通过设置两种 http header 实现:Expires 和 Cache-Control

1.Expires

Exprires 的值指定资源到期的时间,是服务器端的具体的时间点。当再次请求时的请求时间小于返回的此时间,则直接使用缓存数据。但由于服务端时间和客户端时间可能有误差,这也将导致缓存命中的误差,另一方面,Expires 是 HTTP1.0 的产物,故现在大多数使用 Cache-Control 替代。

2.Cache-Control

Cache-Control有很多属性,不同的属性代表的意义也不同。
private:响应只可以被客户端缓存
public:响应可以被客户端和代理服务器缓存
max-age=t:缓存内容将在t秒后失效
no-cache:是否使用缓存需要经过协商缓存来验证决定(需要注意的是,no-cache这个名字有一点误导,并不是说浏览器就不再缓存数据
no-store:不缓存任何响应,既不使用强缓存,也不使用协商缓存

协商缓存:会向服务器发送请求,有服务器根据请求头中的标识来决定是否使用缓存

  • 协商缓存生效,返回 304 和 Not Modified
  • 协商缓存失效,返回 200 和请求结果
  1. Last-Modified

    浏览器在第一次访问资源时,服务器返回资源的同时,在 response header 中添加 Last-Modified 的 header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和 header;

    if-Modified-Since: 浏览器再次请求服务器的时候,请求头会包含此字段,后面跟着在缓存中获得的最后修改时间。服务端收到此请求头发现有 if-Modified-Since,则与被请求资源的最后修改时间进行对比,如果一致则返回 304 和响应报文头,浏览器只需要从缓存中获取信息即可。

  2. Etag

    Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成,生成规则由服务器决定),只要资源有变化,Etag 就会重新生成。 浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的 Etag 值放到 request header 里的 If-None-Match 里,服务器只需要比较客户端传来的 If-None-Match 跟自己服务器上该资源的 ETag 是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现 ETag 匹配不上,那么直接以常规 GET 200 回包形式将新的资源(当然也包括了新的 ETag)发给客户端;如果 ETag 是一致的,则直接返回 304 知会客户端直接使用本地缓存即可。

  3. 两者对比

    • 精确度上,Etag 要优于 Last-Modified

      Last-Modified 的时间单位是秒,如果某个文件在 1 秒内改变了多次,那么他们的 Last-Modified 其实并没有体现出来修改,但是 Etag 每次都会改变确保了精度;

    • 第二在性能上,Etag 要逊于 Last-Modified,毕竟 Last-Modified 只需要记录时间,而 Etag 需要服务器通过算法来计算出一个 hash 值

    • 第三在优先级上,服务器校验优先考虑 Etag

缓存机制

强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。

应用场景

1.频繁变动的资源(一般用协商缓存 Cache-Control: no-cache)

对于频繁变动的资源,首先需要使用 Cache-Control: no-cache 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。

2.不常变化的资源 (一般使用强缓存 Cache-Control: max-age=31536000)

通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000 (一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。
在线提供的类库 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用这个模式。