一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第25天,点击查看活动详情。
前言
当前项目组使用的前端技术栈是vue+docker+nginx。项目一直正常运转,但是有一个特让人头疼的问题。那就是每次部署上线后就会有各种小问题,这个时候我一般会说:要不你清一下缓存?清缓存100%能解决问题。一直因为忙,没时间去处理,这段时间抽了点空,把问题解决了,顺道记录下。
缓存
缓存,是一种本地保存远端资源的一种机制。当使用相同的URL进行数据请求的时候,可以直接从缓存中取资源而不再访问服务器。从这个角度来看缓存是一种能改善性能以及用户体验的一种手段。他可以减少对网络贷款的消耗,减少网络延迟,从而加快网页打开机制。但是凡事都事两面的。缓存也不例外。他也同时具备一些让用户头疼的问题,那就是在某种情况下,依旧从缓存中取资源,但是实际服务器上资源已更新。 web缓存一般是按照其缓存数据存储的位置来区分。总共分为4类缓存。分别是:
- 数据库缓存
- 服务器缓存
- CDN缓存
- 浏览器缓存 由于本章主要解决部署上线浏览器缓存问题,所以本章主要讲浏览器缓存,后续会单独说明其他3个缓存
浏览器缓存
当前浏览器是通过http或者https请求来获取数据的,跟缓存命名一样,其也根据存储的位置来分为4大类。分别是:
- Service Worker 缓存
- 内存缓存
- 硬盘缓存
- 推送缓存 当我们通过URL请求数据的时候会从这4类缓存中取数据,当数据没有取到会通过请求从服务器请求。
Service Worker缓存
Service Worker是一个单独的线程,其与浏览器主线程是相互独立的。其消耗的内存不会堵塞主线程,因此一般处理需要消耗大量资源或者需要离线缓存的场景。如果需要使用Service Worker必须通过HTTPS请求,因为Service Worker可以劫持数据,如果通过HTTP请求会导致安全性的问题。下面就是一个最常见的Service Worker的示例:
window.addEventListener('load', function() {
navigator.serviceWorker.register('/test.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
上面的代码说明,当页面加载完成后,注册/test.js.这个test.js文件按照这个配置是放服务器根目录下。当加载完成后,在Chrome浏览器中输入chrome://inspect/#service-workers查看你注册的缓存
内存缓存
也叫Memory Cache。是不是很熟悉。是的,当我们打开Chrome浏览器的控制台。其中的Memory就是显示的这个。其主要包含当前页面中已经被"抓取"的资源(CSS,JS,Image等)。当我们关闭了浏览器的tab页的时候,其缓存会被释放掉。一般会将小文件缓存在内存中,原因也很简单:因为不管是计算机还是移动设备,其内存一般都是比较小的。
硬盘缓存
也叫Disk Cache。是存储在硬盘中的缓存。由于读取其缓存的数据需要从硬盘中读取,因此他比内存缓存的读取速度要满。但是同样的因为其存储在硬盘中,所以他可以存储体积更大的数据。我们常见的硬盘缓存,可以通过Chrome浏览器中的Source页来看出,其中有一项Size中表明了是否是硬盘缓存数据。如图:
硬盘缓存的原理是通过HTTP请求头中的字段去判断如何取数据:是直接缓存,还是取缓存中数据。还是过期了需要重新请求
推送缓存
也叫Push Cache,一般是当以上三种缓存没有找到数据,就会从推送缓存中取数据。他存在一个Session中。当会话结束后就消除。因此其缓存时间比较短暂。
缓存类型
当前根据缓存的过程,我们可以将向服务器发送请求的缓存,分为两类:
- 强制缓存
- 协商缓存
强制缓存
浏览器在加载资源的时候,会检查缓存是否存在,当不存在的情况下会直接向服务器请求,当缓存存在的时候会判断是否过期,如果过期,同样向服务器发送请求,反之直接存缓存中取数据。其过程如下图:
那么浏览器如何判断缓存是否过期呢,这个时候就需要两个响应头了,那就是
Expires和Cache-Control.
Expires
设置缓存过期时间。在第一次请求的时候,后端会在响应头增加此字段。当再次请求同样请求的时候,会对比当前时间与Expires设置的过期时间,来判断是否过期。但是单纯的通过Expires来判断是有问题的,原因有以下两点:
- 服务器和浏览器的时间可能会不同,这在我们日常开发中经常会遇到,这样就会导致过期时间不准确
- 我们可以修改电脑的系统时间,这样就会导致缓存失效,或者继续使用本身应该失效的缓存。
因此就有了另一个字段
Cache-Control.
Cache-Control
Cache-Control是设置浏览器相对过期时间。以毫秒为单位。其可设置的值见下表:
| 名称 | 描述 |
|---|---|
| no-cache | 走协商缓存,每次从缓存中取数据前都会向服务端确认缓存资源是否更新 |
| no-store | 禁止缓存任何数据 |
| public | 表示缓存为共有缓存,可被代理服务器缓存,也可被多个用户共享 |
| private | 表示私有缓存,与public对立,不能被代理服务器缓存,也不能被多个用户共享 |
| max-age | 表示最大的缓存时间,以秒为单位,当过去后需要重新从服务器获取 |
| must-revalidate | 当缓存过期时,需要去服务端校验缓存的有效性 |
| max-stale | 请求方header中,机试缓存过期,在max-stale时间内还可以使用缓存(代理服务器中) |
以上配置都可以重复设置,但是no-store优先级最高。例如:
cache-control:max-age = 2048000
cache-control:public
此配置说明该缓存的有效期为2048000秒,且是共有的缓存,可以被代理服务器缓存,也可以被多个用户共享。
协商缓存
协商缓存,顾名思义,他会跟去商量下是否缓存以及是否更新缓存。其不指定缓存的有效时间。并且在请求的时候会将表示传给服务器,来确认是否更新缓存。如果返回状态码为304.则表明缓存有效,从缓存中取数据,如果返回的状态码是200。则直接返回最新的数据。以及表示。其过程如下:
那么资源标识是啥呢?当前机制中资源标识有两种:
- Last-Modified
- Etag
Last-Modified
资源最后修改的时间,通过响应头来返回此数据,标识资源最后修改的时间,如果带有Last-Modified。再一次请求的时候,会将请求头带上if-Modified-Since,交给服务器,由服务器根据if-Modified-Since,判断资源是否变化,如果变化返回200以及新的资源标识,反之,返回304 Not Modified。其过程如下图:
但是单纯的通过
Last-Modified依旧会存在判断不准确的情况。那就是:
- 由于有些服务器不能精准的得到文件的最后修改时间,会导致判断不准备
- 当一个文件删除再还原,其内容其实没有边,但是实际上服务器会认为改变了。会导致重新请求
Last-Modified采用的是毫秒精度,因此在短时间内发生的变化,会导致重新请求。 基于以上三个问题,我们就需要另一个资源标识ETag
ETag
被请求变量的实体值。其值是服务器端针对文件所产生的一种文件哈希值。服务器根据此文件哈希值来精准判断缓存。当第一次请求的时候,服务器会在响应头中返回ETag数据。其值为资源文件的Hash值。当再次请求的时候,会在请求头上增加If-None-Match,值为上一次返回的ETag值。服务器根据请求头中的If-None-Match值跟服务器资源文件的Hash值对比。如果相同,返回304 Not Modified。反之返回资源,同时重新返回文件的Hash值放入响应头的ETag中.其过程如下:
优先级问题
在强制缓存中:cache-control的max-age优先级高于Expires
在协商缓存中:ETag的优先级要高于Last-Modified.
因此我们可以得知整个浏览器缓存的过程如下图:
(下次补充)
本章我们介绍了缓存,下一章我们将进入主题,那就是在Vue项目中如何自动的清除浏览器的缓存呢?敬请期待。