浏览器的缓存机制
一、缓存机制概述
1. 基本概念介绍
浏览器缓存机制(即HTTP缓存机制),根据HTTP报文的缓存标识来决定使用缓存内容,还是重新从服务器获取最新内容。可以根据是否需要向服务器重新发起HTTP请求,将缓存过程分为两个部分,分别是强制缓存和协商缓存。
好处
- 提高静态资源的加载速度
- 提高页面渲染速度(取决于资源加载速度)
2. 缓存过程分析
- 当浏览器首次向服务器发起请求,获取到请求结果;
- 根据响应报文中HTTP头的缓存标识判断,是否进行缓存资源;
- 如果存在缓存标识,则将缓存标识和请求结果存入浏览器缓存;
- 再次发起同个请求时,先从浏览器缓存查找缓存标识和请求结果;
- 根据缓存标识判断,是否向服务器发起请求,若缓存标识未过期,直接使用浏览器缓存结果;
- 否则需要向服务器验证,则根据服务器返回结果,判断使用浏览器缓存的请求结果,还是使用服务器返回的新请求结果。
- 如果服务器返回了新的请求结果,重新将缓存标识和请求结果存入浏览器缓存。
二、强制缓存
1. 字段说明
Expires
Expires
指定资源过期的GMT绝对时间,绝对值,如果客户端的当前时间不超过该时间,则直接使用缓存。此字段是HTTP/1.0
控制网页缓存的字段。
比如 0, 代表着过去的日期,即该资源已经过期。
现在浏览器默认使用的是
HTTP/1.1
,那么expires
还能用吗?
可了解HTTP消息
其实,在HTTP/1.1
中使用了Cache-Control
代替,因为Expires
用客户端时间和服务器返回的时间进行比较,但是如果客户端和服务器位于不同时区,造成误差,那么强制缓存则直接失效了。而Cache-Control
是通过设置几秒后失效,这种方式相比Expires
更可靠些。
Cache-Control
Cache-Control
表示控制缓存的策略,其常用属性如下(具体参考Cache-Control):
-
public
:所有内容都将被缓存(客户端和代理服务器都可缓存) -
private
:所有内容只有客户端可以缓存,Cache-Control的默认取值 -
no-cache
:客户端缓存内容,但是是否使用缓存则需要经过协商缓存来验证决定 -
no-store
:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存 -
max-age=xxx
:缓存内容将在xxx秒后失效,相对值
Cache-Control
优先级比Expires
高,所以两者同时存在时,只有Cache-Control
生效。
2. 三种强制缓存情况
- 不存在缓存结果和缓存标识,强制缓存失效,直接向服务器发起请求(相当于第一次发起请求)。
- 存在缓存结果和缓存标识,但已失效,强制缓存失效,则使用协商缓存(下面将具体介绍)。
-
存在缓存结果和缓存标识,但未失效,则使用强制缓存。
三、协商缓存
当浏览器准备第二次请求资源,如果浏览器中已经存在缓存结果和缓存标识,经过浏览器验证已失效,则会向服务器发送请求,服务器会根据请求头中的信息再次验证是否可以使用缓存。
如果验证通过,则返回HTTP状态码304,告诉浏览器资源未更新,可使用本地的缓存。否则,返回HTTP状态码200,及最新的资源内容和缓存标识。
1. 缓存情况
304使用缓存
200使用新资源
2. 字段说明
Last-Modified / If-Modified-Since
Last-Modified
是服务器响应请求时,返回该资源文件在服务器最后被修改的时间,在Response Headers
响应头信息中。
If-Modified-Since
是客户端再次发起该请求时,携带上次请求返回的Last-Modified
值,通过此字段值告诉服务器该资源上次请求返回的最后被修改时间。在Request Headers
请求头信息中。
服务器收到该请求,发现请求头含有If-Modified-Since
字段,则会根据If-Modified-Since
的字段值与该资源在服务器的最后被修改时间做对比。
若服务器的资源最后被修改时间大于If-Modified-Since
的字段值,则重新返回资源,状态码为200
;否则返回304
,代表资源无更新,可继续使用缓存文件。
不足:
- 内容没有变化,但是修改日期发生变化(如周期性重写);
- 内容变化不重要,不需要所有的缓存数据都重写入新数据(如对拼写和注释修改);
- 服务器无法准确判断资源的最后修改日期(如动态生成的页面);
- 资源在毫秒级时间内发生变化(
If-Modified-Since
只能精确到秒);
Etag / If-None-Match
Etag
是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),资源被修改后,Etag
也会随之发生变化。Etag
一般是由文件内容hash生成的,也可由文件描述符、修改时间、大小生成。在Response Headers
响应头信息中。
其中,Etag
有强弱验证器之分。只要内容发生了变化,强验证器就会变化。HTTP/1.1
支持弱验证器,如果只对内容进行了少量修改 ,就允许服务器声明该Etag
未变化。服务器用前缀W/
来标识Etag
为弱验证器。
If-None-Match
是客户端再次发起该请求时,携带上次请求返回的唯一标识Etag
值,通过此字段值告诉服务器该资源上次请求返回的唯一标识值。在Request Headers
请求头信息中。
服务器收到该请求后,发现该请求头中含有If-None-Match
,则会根据If-None-Match
的字段值与该资源在服务器的Etag
值做对比,一致则返回304
,代表资源无更新,继续使用缓存文件;不一致则重新返回资源文件,状态码为200
。
Etag / If-None-Match
优先级比Last-Modified / If-Modified-Since
高,所以两者同时存在时,只有Etag / If-None-Match
生效。
3. 协商缓存和强制缓存的关系
强制缓存优先于协商缓存进行,若强制缓存(Expires
和Cache-Control
)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since
和Etag / If-None-Match
),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,返回200
并存入浏览器缓存中;生效则返回304
,继续使用缓存。
四、浏览器的存储位置
当打开页面https://open.hand-china.com/dashboard
(随便一个网页都可),然后点击刷新,打开控制台可以看到请求数据。从下图可看出,状态码为灰色的请求,表示使用了强制缓存。memory cache
和 disk cache
表示缓存存放的位置。
1. 内存缓存(from memory cache)
from memory cache
代表使用内存中的缓存,适合频繁读取的资源。一般存放js文件、图片、字体等。
- 快速读取:将编译解析后的文件存放进内存,占据进程内存资源,能快速读取。
- 时效性:进程一关闭,就清空内存。
2. 硬盘缓存(from disk cache)
from disk cache
则代表使用的是硬盘中的缓存。硬盘内存,则将缓存写入硬盘文件,读取缓存时需要进行I/O操作,然后重新解析缓存内,读取比内存缓存慢,但容量大、存储时间长。一般存放css文件。
浏览器中,读取缓存的顺序为memory –> disk
。
3. Service Worker
Service Worker
是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker
的话,传输协议必须为 HTTPS
。因为 Service Worker
中涉及到请求拦截,所以必须使用 HTTPS
协议来保障安全。Service Worker
的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。
Service Worker
实现缓存功能一般分为三个步骤:
- 首先需要先注册
Service Worker
- 然后监听到
install
事件以后就可以缓存需要的文件 - 那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。
当 Service Worker
没有命中缓存的时候,我们需要去调用 fetch
函数获取数据。也就是说,如果我们没有在 Service Worker
命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache
中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker
中获取的内容。
4. 推送缓存(Push Cache)
Push Cache
(推送缓存)是 HTTP/2
中的内容,当以上三种缓存都没有命中时,它才会被使用。它只在会话(Session
)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,在Chrome
浏览器中只有5分钟左右,同时它也并非严格执行HTTP
头中的缓存指令。
有如下一些特性:
- 所有的资源都能被推送,并且能够被缓存,但是
Edge
和Safari
浏览器支持相对比较差。 -
Push Cache
中的缓存只能被使用一次 - 可以给其他域名推送资源
- 浏览器可以拒绝接受已经存在的资源推送
- 一旦连接被关闭,
Push Cache
就被释放 - 可以推送
no-cache
和no-store
的资源 - 多个页面可以使用同一个
HTTP/2
的连接,也就可以使用同一个Push Cache
。这主要还是依赖浏览器的实现而定,出于对性能的考虑,有的浏览器会对相同域名但不同的tab标签使用同一个HTTP连接。
五、缓存相关状态码
-
200:强制缓存
Expires/Cache-Control
失效时,返回新的资源文件 -
200(disk cache/memory cache):强制缓存
Expires/Cache-Control
两者都存在,未过期,Cache-Control
优先Expires
时,浏览器从本地获取资源成功。浏览器强制缓存校验通过结果。
-
304(Not Modified):协商缓存
Last-modified/Etag
没有过期时,服务端返回状态码304。服务器协商缓存校验通过结果。
六、刷新操作下的缓存资源情况
F5刷新
刷新某个页面,浏览器设置max-age:0
,并在请求头中携带缓存资源中的Last-Modified
和Etag
,强制跳过浏览器新鲜度检查(即强制缓存的校验),直接发起请求到服务器进行协商校验,服务器验证通过则使用缓存。如下,
请求头
响应头
ctrl+F5刷新
刷新某个页面,浏览器设置Cache-Control: no-cache
,并且不携带缓存资源中的Last-Modified
和Etag
,强制跳过浏览器新鲜度检查(即强制缓存的校验),直接发起请求到服务器,服务器没有获取到If-None-Match
和If-Modified-Since
,无法命中再验证,则返回200,资源以及缓存信息。
请求头
响应头
七、选择合适的缓存
- 请求顺序
Expires/Cache-Control
:请求服务器之前,浏览器进行对比
Last-Modified / If-Modified-Since
、Etag / If-None-Match
:请求服务器进行对比 - 协商缓存配合使用强制缓存。如果不开启强制缓存,协商缓存开启也没什么根本意义。
- 一般web服务器同时开启协商缓存
Last-Modified / If-Modified-Since
、Etag / If-None-Match
。 - 分布式系统里多台机器间文件的
Last-Modified
必须保持一致,以免负载均衡到不同机器导致比对失败; - 分布式系统尽量关闭掉
ETag
(每台机器生成的ETag
都会不一样);