清理下缓存就好了?详解前端必须掌握的缓存

作者:微信小助手

发布时间:2020-02-24T10:45:41


本文15391字,阅读大约需要39分钟。”


缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。

说实话,我起始真的不知道怎么去介绍缓存,所以引用了上面相对官方的定义。我想几乎每个开发者都碰到过缓存的问题吧,甚至有很多情况下我们会说这个问题已经修复了,你清理下缓存就好了。这篇文章我们就细细的来挖掘下缓存的种种轶事。

🦋缓存的种类

很多开发者习惯把cookie、webStorage以及IndexedDB存储的数据也称之为缓存,理由是都是保存在客户端的数据,没有什么区别。其实这是不严谨的,cookie的存在更多的是为了让服务端区别用户,webStorage和IndexedDB则更多用在保存具体的数据和在客户端存储大量结构化数据(文件/blobs)上面。
实际上所谓的缓存只有一种——它是请求资源的副本。试想一下,如果每一个资源我们客户端都会保存一份副本,这会怎么样?客户端会炸掉,开发者会疯掉!所以我们需要一份协议来处理缓存,可以让开发者控制缓存的建立和删除。谁呢?还能有谁,HTTP呗。HTTP协议里定义了很多关于缓存的请求和响应字段,这也是接下来我们重点要逼逼叨的对象,研究下究竟是哪些字段怎么影响缓存的。

纳尼?你问我为什么要缓存?😱

那就太容易说道了🤣,缓存好处有很多:

1. 缓解服务器压力(不用每次去请求资源);
2. 提升性能(打开本地资源速度当然比请求回来再打开要快得多)
3. 减少带宽消耗(我相信你可以理解)

🤦‍♀️那么问题又来了,既然缓存这么好,如果我请求的服务器中间有代理也缓存了怎么办?代理服务器缓存了我的资源导致我没法从源服务器拿到最新的资源怎么办?HTTP当然也想到了这块的诉求。接下来我们也会逐层剖析。

🍉缓存在宏观上可以分成两类:私有缓存共享缓存。共享缓存就是那些能被各级代理缓存的缓存(咋觉得有点绕)。私有缓存就是用户专享的,各级代理不能缓存的缓存。

🐜微观上可以分下面三类:

1. 浏览器缓存

我相信只要你经常使用某个浏览器🌎(Chrome,Firefox,IE等),肯定知道这些浏览器在设置里面都是有个清除缓存功能,这个功能存在的作用就是删除存储在你本地磁盘上资源副本,也就是清除缓存。
缓存存在的意义就是当用户点击back按钮或是再次去访问某个页面的时候能够更快的响应。尤其是在多页应用的网站中,如果你在多个页面使用了一张相同的图片,那么缓存这张图片就变得特别的有用。😏

2. 代理服务器缓存

代理服务器缓存原理和浏览器端类似,但规模要大得多,因为是为成千上万的用户提供缓存机制,大公司和大型的ISP提供商通常会将它们设立在防火墙上或是作为一个独立的设备来运营。(下文如果没有特殊说明,所有提到的缓存服务器都是指代理服务器。)

由于缓存服务器不是客户端或是源服务器的一部分,它们存在于网络中,请求路由必须经过它们才会生效,所以实际上你可以去手动设置浏览器的代理,或是通过一个中间服务器来进行转发,这样用户自然就察觉不到代理服务器的存在了。🤥

代理服务器缓存就是一个共享缓存,不只为一个用户服务,经常为大量用户使用,因此在减少相应时间和带宽使用方面很有效:因为同一个缓存可能会被重用多次。

3. 网关缓存

也被称为 代理缓存或反向代理缓存,网关也是一个中间服务器,网关缓存一般是网站管理员自己部署,从让网站拥有更好的性能。🙂
CDNS(网络内容分发商)分布网关缓存到整个(或部分)互联网上,并出售缓存服务给需要的网站,比如国内的七牛云、又拍云都有这种服务。

4. 数据库缓存

数据库缓存是指当我们的应用极其复杂,表自然也很繁杂,我们必须进行频繁的进行数据库查询,这样可能导致数据库不堪重负,一个好的办法就是将查询后的数据放到内存中,下一次查询直接从内存中取就好了。关于数据库缓存本篇不会展开。🙃

🦄浏览器的缓存策略

缓存的目标:

  • 一个检索请求的成功响应: 对于 GET请求,响应状态码为:200,则表示为成功。一个包含例如HTML文档,图片,或者文件的响应

  • 不变的重定向: 响应状态码:301

  • 可用缓存响应:响应状态码:304,这个存在疑问,Chrome会缓存304中的缓存设置,Firefox

  • 错误响应: 响应状态码:404 的一个页面

  • 不完全的响应: 响应状态码 206,只返回局部的信息


  • 除了 GET 请求外,如果匹配到作为一个已被定义的cache键名的响应


以上,对于我们可以和应该缓存的目标有个了解。🤗

浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的。

那么浏览器怎么确定一个资源该不该缓存,如何去缓存呢❓响应头!响应头!响应头!重要的事情说三遍。✌️

我们看🌰:

Age:23146Cache-Control:max-age=2592000Date:Tue, 28 Nov 2017 12:26:41 GMTETag:W/"5a1cf09a-63c6"Expires:Thu, 28 Dec 2017 05:27:45 GMTLast-Modified:Tue, 28 Nov 2017 05:14:02 GMTVary:Accept-Encoding

1. 强缓存阶段

以上请求头来自百度首页某个CSS文件的响应头。我去除了一些和缓存无关的字段,只保留了以上部分。我们来分析下,Expires是HTTP/1.0中的定义缓存的字段,它规定了缓存过期的一个绝对时间。Cache-Control:max-age=2592000是HTTP/1.1定义的关于缓存的字段,它规定了缓存过期的一个相对时间。优先级上当然是版本高的优先了,max-age > Expires
这就是强缓存阶段,当浏览器再次试图访问这个CSS文件,发现有这个文件的缓存,那么就判断根据上一次的响应判断是否过期,如果没过期,使用缓存。加载文件,OVER!✌️
Firefox浏览器表现为一个灰色的200状态码。
Chrome浏览器状态码表现为:

200  (from disk cache)或是200 OK (from memory cache)

多说一点:关于缓存是从磁盘中获取还是从内存中获取,查找了很多资料,得出了一个较为可信的结论:Chrome会根据本地内存的使用率来决定缓存存放在哪,如果内存使用率很高,放在磁盘里面,内存的使用率很高会暂时放在内存里面。这就可以比较合理的解释了为什么同一个资源有时是from memory cache有时是from disk cache的问题了。
那么当这个CSS文件过期了怎么办?ETagLast-Modified就该闪亮登场了。
先说Last-Modified,这个字段是文件最后一次修改的时间;
ETag呢?ETag是对文件的一个标记,嗯,可以这么说,具体生成方式HTTP并没有给出一个明确的方式,所以理论上只要不会重复生成方式无所谓,比如对资源内容使用抗碰撞散列函数,使用最近修改的时间戳的哈希值,甚至只是一个版本号。

2. 协商缓存阶段

利用这两个字段浏览器可以进入 协商缓存阶段,当浏览器再次试图访问这个CSS文件,发现缓存过期,于是会在本次请求的请求头里携带If-Moified-SinceIf-None-Match这两个字段,服务器通过这两个字段来判断资源是否有修改,如果有修改则返回状态码200和新的内容,如果没有修改返回状态码304,浏览器收到200状态码,该咋处理就咋处理(相当于首次访问这个文件了),发现返回304,于是知道了本地缓存虽然过期但仍然可以用,于是加载本地缓存。然后根据新的返回的响应头来设置缓存。(这一步有所差异,发现不同浏览器的处理是不同的,chrome会为304设置缓存,firefox则不会)😑
具体两个字段携带的内容如下(分别和上面的Last-ModifiedETag携带的值对应):
If-Moified-Since: Tue, 28 Nov 2017 05:14:02 GMTIf-None-Match: W/"5a1cf09a-63c6"
到这协商缓存结束。

3. 启发式缓存阶段

我们把上面的响应头改下:
Age:23146Cache-Control: publicDate:Tue, 28 Nov 2017 12:26:41 GMTLast-Modified:Tue, 28 Nov 2017 05:14:02 GMTVary:Accept-Encoding
发现没?浏览器用来确定缓存过期时间的字段一个都没有!那该怎么办?有人可能会说下次请求直接进入协商缓存阶段,携带 If-Moified-Since呗,不是的,浏览器还有个启发式缓存阶段😎
根据响应头中2个时间字段 Date 和 Last-Modified 之间的时间差值,取其值的10%作为缓存时间周期。
这就是启发式缓存阶段。这个阶段很容让人忽视,但实际上每时每刻都在发挥着作用。所以在今后的开发过程中如果遇到那种 默认缓存的坑,不要叫嚣,不要生气,浏览器只是在遵循启发式缓存协议而已。

👌对于缓存策略介绍到这,�