前言
众所周知,web开发过程中,网页资源响应速度一直是作为网站性能好坏的重要评估标准之一,而缓存技术一直以来在WEB技术体系中都扮演着非常重要的角色,通过缓存技术以及相应的缓存命中策略可以缩短网络请求时间,通过将耗时计算以及耗时传输的结果存起来,在不发生变更的情况下直接复用该结果,从而在保证结果正确性的同时大大提高网站的性能。
在实际WEB开发过程中,缓存技术会涉及到不同层、不同端,比如:用户层、系统层、代理层、前端、后端、服务端等,每一层的缓存目标都是一致的,就是尽快返回请求数据、减少延迟,但每层使用的技术实现是各有不同,面对不同层、不同端的优劣,选用不同的技术来提升系统响应效率。如图展示了各层相关的缓存技术:
如上图所示,浏览器缓存属于用户层的缓存技术,它的主要任务就是一个把已经请求过的web资源(html,js,css,image…)拷贝一份副本到本地,在下一次再次请求该资源时,根据缓存命中机制选择是使用这份副本来直接响应请求,还是向源服务器重新请求最新的资源。
浏览器缓存也是前端开发中最常会接触到的缓存技术,当我们开发了最新的版本之后,需要将打包结果也就是静态资源(html,js,css)部署到服务器上。此时因为浏览器缓存,可能存在即使已经在服务器上更新了资源,但是用户进入网站时并不会看到最新的版本,这里就需要我们了解浏览器的缓存,从而避免这种情况的发生;同时还要利用缓存技术,缓存不常更新的静态资源,提高用户打开网页的速度。
接下来就详细的了解一下浏览器缓存。
查看浏览器缓存
缓存对象
首先浏览器缓存的对象是一次HTTP响应报文的整体,包括响应行,响应头和响应体。其中,常见的HTTP请求方法中只能存储GET响应。通过在firefox的about:cache我们可以查看到浏览器缓存的HTTP响应:
打开baidu.com,查看network控制面板,找到任意一个js资源,记住资源名:
查看firefox缓存:
disk cache的所有缓存内容列表:
找到刚才的js资源名,打开查看缓存内容:
但是并不是所有的GET响应都会被浏览器缓存,只要状态码为以下这些时,响应才会被缓存:
200:一个检索请求的成功响应- HTML文档、js、css、图片、字体文件等
301:永久重定向404:错误响应206:不完全响应
资源缓存位置
浏览器获取缓存的顺序为 Service Worker Cache、Memory Cache、Disk Cache、(至于 Push Cache 属于 HTTP2 待验证)。
from memory cache:是把资源存到内存中,当进程退出时(关闭浏览器),内存中的数据会清空,下次访问要执行别的缓存策略。from disk cache:是把资源缓存在磁盘中,进程退出时不受影响,下次访问可以继续执行此次缓存策略。Sevice Worker Cache(https):开发者人为存储的永久性存储,用于离线缓存的处理。Application -> Cache Storage查看。Push Cache(http2):Push Cache 是 HTTP2 在 sever push 阶段存在的缓存。
浏览器缓存策略
缓存命中机制主要分为两个阶段:强缓存和协商缓存。其中无论是哪种缓存命中,最终使用的都是浏览器缓存到本地的资源。区别在于强缓存不发生网络请求。
强缓存主要是浏览器自行判断资源是否过期,如果不过期则直接使用缓存的资源(强缓存命中),不再进行网络请求;
协商缓存则是在强缓存阶段无法命中的情况下,浏览器发起请求,询问服务器是否可以使用本地缓存资源,如果服务器检查发现浏览器本地的资源没有过期,则返回304告诉浏览器可以使用本地的缓存资源(协商缓存命中);
如果强缓存和协商缓存都没有命中的情况下,服务器会返回最新的资源,浏览器拿到资源后会更新缓存的资源信息。
当前资源是否能被缓存以及是否能通过浏览器缓存机制命中(浏览器是否启动缓存),主要是服务器来设置。当该资源首次被请求时,**服务器通过设置HTTP响应的响应头来设置该资源的缓存信息。**主要是以下几个字段:
Expirescache-controlEtagLast-Modified
强缓存
强缓存是利用
Expires或者Cache-Control这两个http response header实现的,它们都用来表示资源在客户端缓存的有效期。
强缓存就是直接由浏览器判断缓存是否命中,不经过网络。当浏览器发起HTTP请求前,首先会查看当前的缓存列表,找到该资源的响应头,拿到Cache-Control或者Expires字段,通过对两个字段判断强缓存是否命中。当强缓存命中时,HTTP状态码为200,资源从缓存中加载( from memory cache / from disk cache)。
优先级:其中Cache-Control 的优先级大于Expires ,其实还有个Pragma 字段,它的优先级最高,但是不常用。
通过控制台Network 面板查看:
Expires (优先级最低)
- HTTP 1.0 用于缓存管理的
header字段,由服务器返回,用GMT格式的字符串表示。 - 值表示一个资源过期的时间,描述的是绝对时间,且该绝对时间属于服务端的时间系统。
- 判断方法:浏览器发起下一次请求时,当前HTTP发起的请求时间
(this http request time) < (expires设置的值),资源没有过期,缓存命中。 - 弊端:
Expires遵循的是服务端的时间系统,而请求时间遵循的是客户端的时间系统,如果两者时间不是一致的,就可能产生误差。例如手动修改本地客户端的时间,那么就可能影响缓存命中结果。
Cache-Control(优先级第二)
为了解决Expires因为客户端和服务器端时间不统一带来的问题,HTTP 1.1 提出了新的header 也就是 Cache-Control ,这个字段使用相对时间,进行比较的时候用的都是客户端的时间,相对来更有效与安全。
- HTTP 1.1
- 值是一个相对时间,以秒为单位,用数值表示
- 判断方法:浏览器发起下一次请求时,当前HTTP发起的请求时间
(this http request time) < (last http request time + cache-control 设置的值),资源没有过期,缓存命中。
Cache-Control: public, max-age=31536000
该header字段的其他取值如下:
| 字段名 | 位置 | 说明 |
|---|---|---|
| no-cache | 请求头,响应头 | 强制客户端向服务器发送请求(禁止强缓存)。这个值不是禁止客户端或者代理服务器缓存响应。 |
| no-store | 请求头,响应头 | 禁止一切缓存。客户端和代理服务器都不能缓存响应。 |
| max-age | 请求头,响应头 | 设置资源(representations)可以被缓存多长时间,单位是秒。 |
| no-transform | 请求头,响应头 | 代理不可更改媒体类型 |
| cache-extension | 请求头,响应头 | 新指令标记(token) |
| s-maxage | 响应头 | 和max-age同理,只不过是针对代理服务器缓存而言。 |
| private | 响应头 | 不能被代理服务器缓存 |
| public | 响应头 | 响应可以被任何缓存区缓存 |
| must-revalidate | 响应头 | 在缓存过期前可以使用,缓存过期以后必须向服务器验证。 |
| proxy-revalidate | 响应头 | 要求中间缓存服务器对缓存的响应有效性需再次确认(代理服务器需要发送请求给服务器端确认资源有效性,不能直接返回缓存) |
| only-if-cached | 请求头 | 从缓存中获取资源 |
| min-fresh | 请求头 | 单位:秒,期望在指定的时间内,响应仍有效 |
| max-stale | 请求头 | 单位:秒, 接受已过期的响应 |
Pragma (优先级最高)
除了 Expires和Cache-Control以外,还有Pragma字段,但是这个字段用得很少,它只有一个值就是 no-cache,含义等同于Cache-Control 取值为 no-cache,表示禁止强缓存,强制客户端发送http请求给客户端。
协商缓存
协商缓存是指,当强缓存没有命中的情况下,浏览器会发送http请求给服务端,此时服务端并不会直接处理请求,而是会再进行一次缓存命中判断,这个就是协商缓存。如果缓存命中成功,则会返回一个304的状态码,告诉浏览器当前资源没有过期。浏览器接收到304响应后,就会使用本地缓存的响应。
304响应是一个只有响应头,响应体为空的响应。
Last-Modified / If-Modified-Since
- 该字段是资源最后的修改时间。
- 浏览器发送请求时,会将上次响应头中的
Last-Modified赋值给 本次请求头中的If-Modified-Since字段。服务端中接收到请求之后,会将这个字段和当前资源最后的修改时间做对比,- 如果
If-Modified-Since(上一次资源修改时间) < 服务器上资源的最后修改时间,则说明当前资源被修改过了,服务端需要返回新的资源给服务端,此时响应200,返回正常的响应。同时这次响应会返回新的Last-Modified值,用于更新浏览器缓存。 - 如果
If-Modified-Since(上一次资源修改时间)≥ 服务器上资源的最后修改时间,则说明没有修改过资源,则返回304状态码,不会返回资源内容。
- 如果
- 弊端:
- 短时间内资源发生了变化,这个字段并不会发生变化,缓存命中可能失效。
- 如果出现了服务器资源因为反复修改,但资源内容并没发生变化,此时浏览器再次请求服务器,实际上应该认为缓存命中(实际内容没有变化),但是此时通过该字段的比较会导致缓存命中失效。
Etag / If-None-Match (优先级最高)
- 为了解决Last-Modifed的缓存命中问题,可以通过
Etag来管理协商缓存命中。 - 该字段是当前资源在服务器的唯一标识(生成规则有服务器决定),是基于文件内容进行编码的,如果文件内容不发生变动,那么该标识不会发生变更。
- 服务端收到响应以后,根据当前资源内容重新生成一份
Etag,比较该值和If-None-Match是否相等,相等则返回304,不相等则返回200和正常响应。但同Last-Modified的区别在于即使服务器重新生成的Etag字段和原来的没有变化,但是因为重新生成了,304响应中同样会返回Etag字段。
缓存流程图示
用户操作执行的缓存策略
一些涉及到缓存判断的用户行为操作
| 行为 | 可以使用的缓存策略 | 说明 |
|---|---|---|
| 在URL输入栏中输入然后回车 / 访问书签 | 强缓存,协商缓存 | |
| 地址栏回车 / 正常重新加载(command + r) | 协商缓存 | 在请求头加入 Cache-Control: max-age=0 使强缓存无效 |
| F5 / 点击工具栏刷新按钮 / 右键菜单重新加载(command + r) | 协商缓存 | 在请求头加入 Cache-Control: max-age=0 使强缓存无效 |
| ctrl + F5 (command + shift + r) | 协商缓存 | 在请求头加入Cache-Control: no-cache 使强缓存无效 |