缓存的意义是什么?
- 加快用户访问速度,提高用户留存率,进而促进转化率的提升
- 节省服务器带宽成本/cdn 服务流量成本
哪些地方存在缓存?
- 浏览器
- cdn 服务器
- 源站服务器(构建缓存)
一般会缓存哪些资源?
- html 文件
- css 文件
- js 文件
- font 文件
- image 文件
以上 5 种比较常见,实际情况肯定不止。
这些文件是如何被缓存到浏览器的
- 浏览器自发行为
- 浏览器根据 http 网络协议响应首部属性进行判断是否缓存
浏览器自发缓存行为比较少见,所以我们着重看一下基于 http 网络协议的缓存,下面是缓存的属性:
响应报文首部的缓存属性:

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

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

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 可能是存在强缓存且强缓存未失效,但服务器资源和服务器接口已经更新,用户访问了旧资源,在旧资源请求了新的接口,导致故障出现。所以用户强刷缓存以后请求到了最新的服务器资源,该问题得以修复。但是我们不禁要问,这个问题到底应该怎么解决?我们明明已经发布了新资源,为什么浏览器没有请求新资源?
我们再来看一个更细的资源请求流程:

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

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

显然,只要不对 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 最符合我们的需求,只有当编译文件的结果发生变化,才会生成新的文件,这样我们就能充分发挥缓存的作用了。调整后的方案:

相信看完上面的例子以后,大家对于场景 1 的情况大概心里有数了,那场景 2 呢?这又是怎么回事?下面就得讲到另一个缓存的地方,CDN 服务器缓存。
CDN 服务器缓存
为了让我们的网站资源更快的送达到用户身边,所以有了 CDN 服务器,相信大部分的面向用户的网站都会接入 CDN 服务器,如何保证 CDN 服务器资源的正确性就显得非常重要。
CDN 示意图:

CDN 如何缓存源站的资源

CDN 是否会存在错误缓存
如果采取不当的发布方式,可能会导致 CDN 缓存错误。例如:先拷贝了 html 页面,后拷贝了 js 等资源,则会导致 html 请求的 js 不存在。所以最稳妥的方式是先拷贝 js 等资源,最后拷贝 html 页面。
如果 CDN 出现了错误缓存怎么办
可以采取刷新 CDN 缓存的方式更新缓存
CDN 的回源策略有哪些?
1、主动回源:刷新 CDN
2、被动回源:用户请求,缓存过期/新请求
讲到这里,场景 2 大家可能就知道大概是怎么回事了,为什么有的人访问正常,有的人访问不正常?可能是因为部分 CDN 节点存在缓存错误。
林秀栋的技术博客