前言
浏览器缓存对开发者来说一直都是一个有爱又恨的存在,一方面帮助开发者提升用户体验,另一方面有时会抽风,读取缓存展示错误的内容,因此,希望对浏览器缓存做一个总结,避免开发时因为缓存机制而过多耗费时间。接下来,就进入浏览器缓存的世界
什么是缓存
缓存是指一个资源存在于服务器和客户端之间的副本,缓存会根据请求保存输出内容的副本,当下一个请求进来的时候,如果是相同的 URL,缓存会根据缓存机制决定是直接使用副本响应还是向源服务器重新请求,当你访问一个网站时,打开调试面板,你会发现几乎静态资源请求都是从缓存中读取出来的,如图:
为什么要缓存
不用缓存可以吗?当然可以,至于后果是什么?试了就知道。用了缓存之后,会有什么好处:
- 减少网络带宽消耗。无论对于网站运营方还是用户,带宽都是和钱挂钩的,当使用缓存时,产生的网络流量是极小的,对于两边都可以降低开销
- 降低服务器压力。当使用缓存时,可以有效减少用户对源服务器的请求,从而降低服务器压力
- 加快网页打开速度,提升用户体验。请求缓存比请求源服务器所话费的时间要短的多,因此内容可以更快的触达用户,以提升体验
浏览器缓存
前菜结束上硬菜,本篇主角浏览器缓存,浏览器缓存是众多 web 缓存分类中的一种,主要分为:
- Memory Cache
- Service Worker Cache
- HTTP Cache
- Push Cache
Memory Cache
Memory Cache 指的是内存中的缓存,它有如下几个特点:
- 响应速度最快,是浏览器请求时最先去尝试命中的缓存
- 生命周期短,一旦进程被关闭就会被清空
- 内存有限,资源存放位置随机
- 不关心资源的 HTTP 缓存头的 Cache-Control 值,在同一个进程会被重用
来看一个例子:
第一张图是第一次打开网页时截取的,圈圈标记的图片时存放在 Disk Cache 里面的,第二张图时网页刷新时截取的,圈圈标记的图片是存放在 Memory Cache 中的,同样的图片两次为什么是从不同的缓存中读取的呢?因为 Memory Cache 进程关闭时就会清空,但是第二次刷新的时候,第一次浏览器解析图片文件进入 Memory Cache,第二次刷新时由于 Memory Cache 是浏览器请求时最先去尝试命中的缓存,因此会直接去从 Memory Cache 中取
Service Worker Cache
Service Worker 是一种独立于主线程之外的 JavaScript 线程,不会对当前程序的执行线程造成堵塞,通过 Service Worker 我们可以自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,通过 Service Worker 实现的缓存称为 Service Worker Cache,如果你对 Service Worker 有更深入了解的兴趣,可以去看看我之前的一篇博客:传送门
HTTP Cache
HTTP Cache 是我们日常开发中接触最多也是最为熟悉的缓存,HTTP Cache 通常可以分为强缓存和协商缓存,缓存策略都是通过 HTTP Header 来实现的,强缓存的优先级高于协商缓存,在命中强缓存失败的情况下才会去命中协商缓存
强缓存
强缓存主要通过设置 HTTP Header Expires 和 Cache-Control 两个字段控制,当请求发出时。浏览器会根据上一次请求时记录的 Expires 和 Cache-Control 来判断是否命中强缓存,若命中则直接从强缓存中取资源,不会再向服务发送请求,当命中强缓存时,HTTP 状态码返回 200,且 Size 显示 from disk cache,如图所示:
Expires
Expires 缓存过期时间,值为一个时间戳。如图:
当我们两次向同一个服务器请求资源时,第二次请求时,浏览器会先对比 Expires 时间戳和本地时间,如果本地时间小于 Expires 时间戳就会去缓存中取资源,但是这样存在一个问题,Expires 依赖客户端时间,如果客户端时间和服务端时间不一致时,就会产生问题,Expires 是 HTTP1.0 标准下的字段,考虑到这个问题,因此在 HTTP1.1 标准下新增了 Cache-Control 字段
Cache-Control
Cache-Control 字段是 Expires 字段的完全替代方案,它做 Expires 字段能做的所有事,还有 Expires 字段不能做的事情,当前还在用 Expires 字段的目的只是向下兼容,Cache-Control 字段包含多个指令,这里介绍几个最常用的:
- max-age:max-age 指令控制资源的有效期,值为时间长度,如图:
当客户端发送的请求中包含 max-age 指令时,浏览器会向服务器确认缓存的有效性,如果判定缓存资源的缓存时间数值比指定的时间数值小,那么客户端就接收缓存资源,当指定 max-age 值为 0 时,通常会向服务器发送请求
当服务器返回的响应中包含 max-age 指令时,表示这段时间内,响应由缓存控制,浏览器不会再向服务器确认资源的有效性,而是直接返回缓存
- s-maxage:s-maxage 指令的功能和 max-age 指令的相同,不同点是 s -maxage 指令只适用于供多位用户使用的公共缓存服务器(代理服务器),s-maxage 的优先级高于 maxage,当 s -maxage 未过期时,会向公共缓存服务器请求缓存
- public 指令与 private 指令:当使用 public 指令时,则表示此缓存是公有缓存,可以被其他用户使用,当使用 private 指令时,表示该缓存时私有缓存,只有在特定用户请求时才会返回缓存
- no-cache:防止从缓存中返回过期资源,当客户端请求中包含 no-cache 指令,表示客户端将不会接收缓存过的响应,缓存服务器必须把客户端请求转发给源服务器,当服务器返回的响应中包含 no-cache 指令,缓存服务器不能对资源进行缓存,存储在本地缓存区中缓存在与源服务器进行新鲜度再验证之前,缓存不能将其提供给客户端使用
- no-store:不使用任何缓存,直接向源服务器请求下载内容
协商缓存
协商缓存就是强制缓存失效后,浏览器浏览器携带缓存标识向服务发起请求,由服务器更具缓存标识决定是否返回缓存,主要有两种情况:
- 如果服务端提示资源未改动,资源会被重定向到浏览器缓存,这种情况下对应的网络状态码为 304,如图:
- 协商缓存失败,资源更新了,重新返回请求结果,这种情况下对应的网络状态码为 200
接下来介绍和协商缓存相关的头部字段:
Last-Modified/If-Modified-since
Last-Modified 指明资源最终修改的时间,值为一个时间戳,如图:
当缓存要对已缓存的文档进行再验证时,请求头中就会包含一个 If-Modified-since 首部,其携带有此资源最后修改的时间戳,如图:
如果在此期间内容被修改,最后的修改日期就会有所不同,源服务器就会返回新的内容重新响应,否则就会返回 304,重定向到浏览器缓存。
使用 Last-Modified 存在两个弊端:
- 我们打开了文件,但是并没有修改文件内容,服务器还是会认为我们修改了这个文件,Last-Modified 会被更新,下次请求时会重新响应
- If-Modified-since 只能感应以秒为最小单位的时间差,当改动文件速度过快,小于 1s 时,无法感知文件变化,导致应该重新请求时,拉到缓存资源
由于这些缺陷 HTTP1.1 出现了 Etag/If-None-Match
Etag/If-None-Match
ETag 能告知客户端实体标识,它是一种可将资源以字符串形式做唯一标识的方式。服务器会为每份资源分配对应的 ETag 值,当资源更新 ETag 值也会更新,如图:
当我们下次请求时,请求头里会带上一个 If-None-Match 字符串提供服务端对比,如图:
如果服务器上的标签已经发生了变化,服务器会在一个 200 响应中返回新的内容以及新的 ETag,否则返回 304 重定向到缓存
HTTP 缓存决策
此图源自 google,清楚展示了 HTTP Cache 决策的过程,对上面介绍的缓存过程做了一个完美的总结
Push Cache
Push Cache 指 HTTP2 在 server push 阶段存在的缓存,是 HTTP2 session 的一部分,不是一个持久化的缓存,当 session 结束时,缓存也会随之结束,不同的页面只要共享了同一个 HTTP2 连接,那么它们就可以共享同一个 Push Cache,如果你对 Push Cache 还有更多的兴趣,这里提供三篇文章供你阅读:
- HTTP/2 push is tougher than I thought
- A Tale of Four Caches
- HTTP2 Server Push 的研究
总结
此篇文章记录总结了浏览器缓存相关的一些知识点,是个人最近对缓存知识的一个总结,希望对大家也能有所帮助。
如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞