聊聊浏览器缓存

1,031 阅读6分钟

前言

最近宅在家学习了一下缓存知识,总结成文档,和大家交流分享。说到缓存,有浏览器缓存、数据库缓存、代理服务器缓存、CDN缓存等,我们今天只聊浏览器缓存(其他的涉及到我的知识盲区)。

很多文章中提到的前端缓存、web缓存也都是浏览器缓存。

缓存位置

我们都知道,浏览器请求时会先看看有没有缓存,命中缓存就直接拿到资源,不用再去向服务器请求。省去了请求的时间,也减轻了服务器的压力。那么缓存都是放在哪里的呢?

  1. Service Worker
  2. Memory Cache
  3. Disk Cache
  4. Push Cache

缓存的四个位置按照优先级顺序依次为Service Worker、Memory Cache、Disk Cache和Push Cache。

Service Worker

Service Worker可以理解成浏览器和服务器之间的一个代理,比起Memory Cache 或是Disk Cache,Service Worker可以让我们开发者来控制缓存哪些文件、如何匹配缓存、如何读取等。出于安全考量,Service Worker只能用HTTPS来承载。

如果Service Worker没有命中缓存,那么就会用fetch() 函数来获取数据。这里需要注意的是:经过fetch()方法获取的资源,无论是从Memory Cache中拿到的数据还是网络请求拿到的,都会显示从Service Worker中拿的。 像这样:

Memory Cache

Memory Cache就是浏览器放在内存中的缓存,内存我们都知道:容量小,但是读取速度快,所以一些小的资源就可以在内存中缓存了。

打开一个网页,打开控制台,刷新一次,就可以看到有的资源是从Memory Cache中拿到的。如图:

图中看到Time那一栏的值,都是0ms(这里是特例,不全都是0ms),可以看到从内存中拿资源是非常快的,但是内存的容量比较小,时效性也比较低,Tab页关掉,这些缓存就会失效了。

Memory Cache里面主要是图片、js 、css文件,preload 预加载来的资源也会放在Memory Cache中。Memory Cache是不关心http首部字段的。设置的max-age或是no-cache都会被浏览器忽略掉。

Disk Cache

真正关心http首部字段的人是Disk Cache,就是硬盘中的缓存。硬盘与上面的内存是互补的,硬盘容量大,时效性长(资源过期时间长),缺点就是读取速度慢。

Disk Cache会根据HTTP请求的响应头来判断哪些资源可以缓存、哪些资源过期了需要重新请求等。即使是不如内存的速度快,但是省去了网络请求的话,速度也还是提升了不少的。绝大部分的缓存都是来自于Disk Cache。

硬盘上的缓存,如果两个站点共用了一张图,是可以共用的。

Push Cache

Push Cache 就是HTTP2的服务器端推送。是服务器端推测我们可能需要的一些资源,提前推送到客户端的缓存里面来,等到客户端需要,直接从缓存里面拿就可以了。

例如,我们请求一个HTML,那么肯定需要这个页面的图片、css、js资源了,服务器端可以提前推送。

好了,我们现在来梳理一下整个流程:

  1. Service Worker,命中缓存,返回资源;否则调用fetch方法
  2. 查看Memory Cache,命中缓存,返回资源;否则到第3步
  3. 查看Disk Cache,命中缓存,返回资源,否则到第4步
  4. 查看Push Cache
  5. 没有命中缓存,进行网络请求
  6. 把响应内容存到Disk Cache
  7. 把响应内容的引用存到memory cache
  8. 把响应内容存入Service Worker

强缓存 & 协商缓存

前面说完了缓存的位置,下面我们聊聊缓存的两种方式:强缓存和协商缓存。

强缓存

强缓存就是服务器端返回响应的时候,告诉客户端多长时间这个资源不失效,直接用就行了,不用来问我。

强缓存主要是用Cache-Control和Expires这两个首部字段来控制。

Expires字段的值是资源的失效时间。像这样:

Thu, 10 Nov 2017 08:46:12 GMT

这个字段的缺陷在于,客户端的时间可以用户手动改掉,导致缓存失效。他是http 1.0的字段,目前基本上不会用了,用到只是为了兼容。大家了解一下就好。max-age的优先级比Expires高。

Cache-Control的max-age字段(单位:秒)可以设置资源在多少秒之后过期。像这样:

Cache-Control:max-age=500

表示500s内这个资源都无须向服务器确认,浏览器再次请求会直接命中缓存,使用缓存就好了。

Cache-Control有很多可选的值,我们说几个常用的。(查看全部值

  • no-store,不允许缓存,每次请求都必须从源服务器请求资源
  • no-cache,可以缓存,但是每次得先去服务器确认是否过期
  • public,代理服务器和客户端都可以缓存
  • private,只有客户端可以缓存
  • s-maxage,和max-age含义一样,但是只对代理服务器有效

协商缓存

如果能次次都命中强缓存那当然很好,省了网络请求,网页的响应速度那绝对的快。但是如果服务器上的资源更新了呢?那完蛋,我们就拿到脏数据了。

所以协商缓存就要起作用了。协商缓存就是客户端虽然缓存了,但是客户端还是得发请求到服务器去确认一下,资源过期没?没-> 304,拿缓存;过期了,200,返回响应内容。

协商缓存的两个首部字段是:ETag和Last-Modified。准确地说是两对儿:ETag & If-None-Match ;Last-Modified & If-Modified-Since。

ETag

ETag 是服务器端产生的一个hash字符串,用来标识一个资源的状态。像这样:

ETag: "82e222983df93"

服务器会为每份资源分配对应的ETag,资源更新时,ETag也会更新。ETag的生成并没有什么统一的算法规则,看服务器怎么生成了。

画个图吧。客户端带过去的If-None-Match就是服务器端返回的ETag,服务器端会把If-None-Match的值和存在服务器端的ETag值对比,一致则说明资源没更新,否则,更新了。

Last-Modified

Last-Modified: Thu, 10 Nov 2017 08:46:12 GMT

Last-Modified是服务器告诉我们资源最后修改的时间。还是上个图吧,简单又清晰。

Last-Modified相比于ETag,缺点在于:

  1. Last-Modified只能精确到秒,如果1秒内,资源改了两次,那就出事了。
  2. 资源的最后修改时间改了,但是内容没改,Last-Modified还是会算作资源被修改了,而ETag不会。

缓存策略应用

目前的项目大多使用这种缓存方案的:

  • HTML: 协商缓存;
  • css、js、图片:强缓存,文件名带上hash。

浏览器行为

  • 地址栏输入网址:查找Disk Cache,命中缓存,拿到资源,否则发起网络请求。
  • 刷新网页:先查看Memory Cache,没有命中缓存的话再去Disk Cache。
  • 强制刷新:不使用缓存,去服务器请求。

以上,如有错漏,恳请指正!

参考文章