林秀栋的技术博客

前端资源缓存方案

文章来源

缓存的意义是什么?

  1. 加快用户访问速度,提高用户留存率,进而促进转化率的提升
  2. 节省服务器带宽成本/cdn 服务流量成本

哪些地方存在缓存?

  1. 浏览器
  2. cdn 服务器
  3. 源站服务器(构建缓存)

一般会缓存哪些资源?

  1. html 文件
  2. css 文件
  3. js 文件
  4. font 文件
  5. image 文件

以上 5 种比较常见,实际情况肯定不止。

这些文件是如何被缓存到浏览器的

  1. 浏览器自发行为
  2. 浏览器根据 http 网络协议响应首部属性进行判断是否缓存

浏览器自发缓存行为比较少见,所以我们着重看一下基于 http 网络协议的缓存,下面是缓存的属性:

响应报文首部的缓存属性:

01

http 网络协议请求报文首部的缓存属性:

02

浏览器缓存的过程是怎样的呢?

03

1、浏览器强制刷新/禁用资源缓存是怎么做到的?

在请求头部配置 cache-control: no-cache 或者 Pragma: no-cache

2、如何设置浏览器的强缓存?

在资源响应头部配置以下任一属性:

1)Expires: Mon, 10 Aug 2020 06:26:14 GMT

2)Cache-Control: max-age=604800

3、协商缓存怎么进行协商?

请求头携带以下属性:

1)If-Modified-Since: Tue, 21 Jul 2020 17:21:36 GMT

2)If-None-Match: W/”5f172420-cd9a2”

响应头返回以下属性:

1)Last-modified: Tue, 21 Jul 2020 17:21:36 GMT

2)Etag: W/”5f172420-cd9a2”

如果 If-Modified-Since 的时间等于 Last-modified 的时间,并且 If-None-Match 的值等于 Etag 的值,则说明服务器资源未发生变更,可以从本地读取缓存资源,反之则说明服务器资源发生变更,需要重新从服务器拉取新资源。

到这里我们大概了解了浏览器缓存是怎样一个过程,那我们再回过头来看一下场景 1,我们猜测场景 1 可能是存在强缓存且强缓存未失效,但服务器资源和服务器接口已经更新,用户访问了旧资源,在旧资源请求了新的接口,导致故障出现。所以用户强刷缓存以后请求到了最新的服务器资源,该问题得以修复。但是我们不禁要问,这个问题到底应该怎么解决?我们明明已经发布了新资源,为什么浏览器没有请求新资源?

我们再来看一个更细的资源请求流程:

04

如果我们请求头没有发生任何变更,在缓存期间无论我们发布多少次,用户都无法访问到新资源。有的同学可能会想到给请求加上版本号或者时间戳这种方案。我们再看看如果用版本号的方案是怎样的。见下图:

05

看完上图,我们大概知道了 html 是不是不应该被缓存?我们再来看看如果不缓存 html 的情况:

06

显然,只要不对 html 进行缓存,再配合版本号请求,是可以解决资源缓存无法更新的问题的,该方案也是很多传统项目所采用的方案。但是这个方案是最佳方案吗?显然不是,采用版本号方案有两个比较明显的问题:

1)每次发布需要手动调整版本号(有同学说,那我可以采用时间戳呀,但时间戳也会存在问题 2)

2)每次发布都会导致全量缓存失效,意味着 1000 个前端静态资源,你只要改了其中 1 个,其余 999 个缓存全部失效,显然这不是我们想要的。

那我们还能怎么改进方案呢?这里我们不得不夸奖一下 webpack 的强大,因为借助 webpack,我们可以更好的发挥缓存的作用。

webpack 的三种 hash 值

1、hash

基于整个项目构建结果生成的 hash 值,只要项目内任何一处发生变化,hash 值都会变化;

2、chunkhash

基于 chunk 构建结果生成的 hash 值,只有 chunk 内的内容发生变化,hash 值才会变化;

例如:

// 修改前 // a.vue => a.fda123fd.js
<template>
  <div class="red">hello world!</div>
</template>
// a.vue => a.fda123fd.css
<style>
  .red {
    color: red;
  }
</style>

// 修改后 // a.vue => a.f123klnk.js
<template>
  <div class="red">hello world!</div>
</template>
// a.vue => a.f123klnk.css
<style>
  .red {
    color: #f00;
  }
</style>

3、contenthash

基于构建结果文件的内容生成的 hash 值,只要文件内容不变化,hash 值不变;

例如:

// 修改前 // a.vue => a.fda123fd.js
<template>
  <div class="red">hello world!</div>
</template>
// a.vue => a.45h6j7k8.css
<style>
  .red {
    color: red;
  }
</style>

// 修改后 // a.vue => a.fda123fd.js
<template>
  <div class="red">hello world!</div>
</template>
// a.vue => a.3df4g56j.css
<style>
  .red {
    color: #f00;
  }
</style>

tips: 需要注意的是,如果 style 存在 scope 属性,即使使用了 contenthash,只调整 template 或者 script,没有调整样式内容,contenthash 也会在每次编译后发生变化。

了解了以上三种 hash 值,显然 contenthash 最符合我们的需求,只有当编译文件的结果发生变化,才会生成新的文件,这样我们就能充分发挥缓存的作用了。调整后的方案:

07

相信看完上面的例子以后,大家对于场景 1 的情况大概心里有数了,那场景 2 呢?这又是怎么回事?下面就得讲到另一个缓存的地方,CDN 服务器缓存。

CDN 服务器缓存

为了让我们的网站资源更快的送达到用户身边,所以有了 CDN 服务器,相信大部分的面向用户的网站都会接入 CDN 服务器,如何保证 CDN 服务器资源的正确性就显得非常重要。

CDN 示意图:

08

CDN 如何缓存源站的资源

09

CDN 是否会存在错误缓存

如果采取不当的发布方式,可能会导致 CDN 缓存错误。例如:先拷贝了 html 页面,后拷贝了 js 等资源,则会导致 html 请求的 js 不存在。所以最稳妥的方式是先拷贝 js 等资源,最后拷贝 html 页面。

如果 CDN 出现了错误缓存怎么办

可以采取刷新 CDN 缓存的方式更新缓存

CDN 的回源策略有哪些?

1、主动回源:刷新 CDN

2、被动回源:用户请求,缓存过期/新请求

讲到这里,场景 2 大家可能就知道大概是怎么回事了,为什么有的人访问正常,有的人访问不正常?可能是因为部分 CDN 节点存在缓存错误。