背景
浏览器缓存机制在性能优化中扮演重要的一环。优秀的缓存策略能够缩短请求网页的距离、降低延迟和网络负荷、减少请求带宽。那该如何应用好浏览器缓存,我们需要对其原理有一定认识。
浏览器缓存机制
浏览器中缓存可分为强缓存和协商缓存。具体判断使用那种缓存机制,是通过 http
header
字段的不同。
浏览器缓存机制过程
- 浏览器加载资源,根据资源的
http
header
判断是否命中强缓存;- 若命中:浏览器直接从自己缓存中读取资源,不会发生
http
请求到服务器; - 没有命中:浏览器会发送请求到服务器,通过服务器中
http
header
验证这个资源是否命中协商缓存;- 命中:请求返回,但不返回资源,告诉客户端可直接从缓存中加载;
- 没有命中:请求返回,返回资源;
- 若命中:浏览器直接从自己缓存中读取资源,不会发生
可以得出:强缓存与协商缓存区别:强缓存不发生请求到服务器,协商缓存会发请求到服务器。
下面,我们需要知道 http header
如何判断命中强缓存和协商缓存的。
强缓存
Expires
Expires: Thu, 21 Nov 2019 07:48:15 GMT
Expires
是 HTTP/1.0
控制网页缓存的字段。其值为服务器返回该请求结果缓存的到期时间,即如果发生时间在 Expires
之前,那么本地缓存始终有效,否则就会发送请求到服务器来获取资源;是绝对时间;
Cache-Control
Cache-Control
是 HTTP/1.1
新增的规则,用于控制网页缓存的字段。
- 示例分析
- cache-control: max-age=2592000,s-maxage=3600
- max-age: 资源第一次的请求时间和
Cache-Control
max-age
设定的有效期,计算出资源过期时间;再拿这个过期时间跟当前的请求时间比较,如果请求时间在过期时间之前,就能命中缓存,否则不行。是相对时间;
- max-age: 资源第一次的请求时间和
- cache-control: public, max-age=31536000
public
可以被所有用户浏览器缓存,包括代理服务器,时长为第一次请求资源时间与31536000秒之和。
Expires & Cache-Control
我们需要知道 Cache-Control 与 Expires 同时存在的话(如下图),Cache-Control** 的优先级高于 **Expires
因为 Expires
时间返回的是服务器绝对时间,而客户端本地时间是可以修改的(时区不同等),造成服务器与客户端时间发生误差,强缓存会直接失效。而 Cache-Control
是相对时间,每次参照客户端第一次请求时间计算而来的,故不会受到影响;毕竟 Cache-Control
是 HTTP/1.1
新增的规范
查看浏览器已经成功应用使用了强缓存
一旦资源命中强缓存, 浏览器便不会向服务器发送请求, 而是直接读取缓存。 Chrome
下的现象如下 200 OK disk cache
或者 200 OK from memory cache
disk cache & memory cache
看到上图,应该会有疑问,from memory cache
和 from disk cache
又分别代表的是什么呢?
根据英文名 memory disk
,我们需要知道内存缓存和硬盘缓存两个概念;
内存缓存
内存缓存即
memory cache
内存缓存有两个特点 快速读取 和 时效性 快速读取:会将解析编译资源放入到进程中的内存,占据一定内存资源,方便下次快速读取 时效性:一旦进程关闭,进程的内存会被清空
硬盘缓存
硬盘缓存即
disk cache
硬盘缓存将数据写入到磁盘文件,读取缓存需要读取硬盘文件进行
I/O
操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。
读取缓存时机
什么时候从内存缓存读取,什么时候从硬盘缓存读取?
在浏览器中,浏览器会在 JS
和图片等文件解析执行后直接存入内存缓存中,那么当刷新页面时,直接从内存缓 存 from memory cache
中读取,而 CSS
比较大的文件则会存入硬盘文件,所以每次渲染页面都需要从磁盘读取缓存 from disk cache
。
- 根据例子解释说明
打开一个网页此时是开启一个新进程,内存中还没缓存文件。所以从硬盘缓存
from disk cache
中读取,如下图
当刷新网页,内存中已经有缓存缓存文件,故有的会从内存缓存
from memory cache
中读取
协商缓存
Last-Modified
Last-Modified: Wed, 21 Nov 2018 05:46:58 GMT
If-Modified-Since: Wed, 21 Nov 2018 05:46:58 GMT
具体过程如下:
- 1、浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,会在
Respone
的Header
的Last-Modified
值设置为,该资源最后修改的时间; - 2、第二次请求的时候,在
Request
的Header
上加上If-Modified-Since
,值为上次请求资源的Last-Modified
; - 3、服务器收到
If-Modified-Since
与服务器文件的Last-Modified
比对,- 命中:无变化则返回
304
,不返回资源。浏览器收到304
使用本地缓存;不更新Last-Modified
; - 不命中:有变化返回
200
,重新更新Last-Modified
、返回200
、返回资源。
- 命中:无变化则返回
ETag
Etag 是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成)。
ETag: "d5d-55b192d5e0640"
If-None-Match: "d5d-55b192d5e0640"
具体过程如下:
- 1、浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,会在
Respone
的Header
的ETag
值设置为,该资源当前资源文件的一个唯一标识; - 2、第二次请求的时候,在
Request
的Header
上加上If-None-Match
,值为上次请求资源的ETag
; - 3、服务器收到
If-None-Match
与服务器文件的ETag
比对,- 命中:一致则返回
304
,代表资源无更新,故不返回资源。浏览器将会收到304
使用本地缓存;更新ETag
; - 不命中:不一致返回
200
,重新更新ETag
、返回200
、返回资源。
- 命中:一致则返回
我们可以得知具体过程与 Last-Modified
过程一致,只有有一点区别如下:
- 当服务器返回
304 Not Modified
的响应时,由于ETag
重新生成过,Respone
的Header
还会把这个ETag
返回,即使这个ETag
跟之前的没有变化。
Last-Modified & Etag
Last-Modified
与 ETag
是可以一起使用的(见下图),服务器会优先验证 ETag
,一致的情况下,才会继续比对 Last-Modified
,最后才决定是否返回 304 Not Modified
。
ETag 可以解决 Last-Modified
存在的一些问题,既生 Last-Modified
何生 ETag
?
- 文件内容不更改,但修改时间发生改变,这时候不希望客户端认为这个文件修改了。
- 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说
1S
内修改了N
次),If-Modified-Since
能检查到的粒度是S
级的,这种修改无法判断; - 某些服务器不能精确的得到文件的最后修改时间。
总结
强缓存会优先于协商缓存,若强缓存 ( Expires
/ Cache-Control
) 生效,直接不发送请求直接使用本地资源;不生效接着进行协商缓存。协商缓存 ( Last-Modified
/ ETag
) 由服务器进行判断,若文件失效,返回新的资源,否则返回 304
,不返回资源。