前端优化:浏览器缓存技术介绍
浅谈浏览器http的缓存机制
Http协议缓存了解一下
一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。所以,缓存技术是无数WEB
开发从业人员在工作过程中不可避免的一大问题。
第一次打开该网站后,如果再次刷新页面。会发现浏览器加载的众多资源中,有一部分size
有具体数值,然而还有一部分请求,比如图片、css
和 js
等文件并没有显示文件大小,而是显示了from disk cache
或者from memory cache
字样。这就说明了,该资源直接从本地硬盘或者浏览器内存读取,而并没有请求服务器。
HTTP与缓存
浏览器是否使用缓存、缓存多久,是由服务器控制的。准确来说,当浏览器请求一个网页(或者其他资源)时,服务器发回的响应的响应头
部分的某些字段指明了有关缓存的关键信息。下面看下,http
报文中与缓存相关的首部字段:
通用首部字段
字段名称 | 说明 |
---|---|
Cache-Control |
控制缓存的行为 |
Pragma |
http1.0遗留的产物,当值为no-cache 时候禁止使用缓存 |
请求首部字段
字段名称 | 说明 |
---|---|
if-Match |
比较 Etag 是否一致 (Etag 是URL 的Entity Tag ,用于标示URL 对象是否改变,区分不同语言和Session 等等。) |
if-None-Match |
比较 Etag 是否不一致 |
if-Modified-Since |
比较资源最后更新的时间是否一致 |
if-Unmodified-Since |
比较资源最后更新的时间是否不一致 |
响应首部字段
字段名称 | 说明 |
---|---|
ETag |
资源匹配的信息 |
实体首部的字段
字段名称 | 说明 |
---|---|
Expires |
http1.0 ,实体主体过期的时间 |
Last-Modified |
资源最后一次修改的时间 |
缓存的判断规则
过期机制
过期机制就是浏览器根据缓存的有效期进行判断,如果在有效期内就使用缓存,否则就抛弃这个缓存。
一个缓存副本必须满足以下条件,浏览器会认为它是有效的,足够新的:
- 含有完整的过期时间控制头信息(
http
协议报头),并且仍在有效期内; - 浏览器已经使用过这个缓存副本,并且在一个会话中已经检查过新鲜度。
验证机制
浏览器带上本地缓存副本的验证信息提交给服务器(Last-Modified,ETag
),由服务器决定是否采用这个缓存。
客户端请求的时候带上Last-Modified
,服务器进行验证返回If-Modified-Since
来确定资源是否是有效缓存。 另外在控制头信息带上这个资源的实体标签Etag
(Entity Tag
),它可以用来作为浏览器再次请求过程的校验标识。如过发现校验标识不匹配,说明资源已经被修改或过期,浏览器需求重新获取资源内容。
缓存来源
from disk cache
此资源是从磁盘当中取出的,也是在已经在之前的某个时间加载过该资源,不会请求服务器但是此资源不会随着该页面的关闭而释放掉,因为是存在硬盘当中的,下次打开仍会from disk cache
。
from memory cache
字面理解是从内存中,其实也是字面的含义,这个资源是直接从内存中拿到的,不会请求服务器一般已经加载过该资源且缓存在了内存当中,当关闭该页面时,此资源就被内存释放掉了,再次重新打开相同页面时不会出现from memory cache
的情况。
强缓存和协商缓存
浏览器在加载缓存的时候,会先根据这个资源的一些 http header
判断它是否命中强缓存。如果命中的话,浏览器直接从自己的缓存中读取资源,不会发送请求给服务器
。比如:某个css
文件,如果浏览器在加载它所在的网页时,这个css
文件的缓存配置命中了强缓存,浏览器就直接从缓存中加载这个css
,连请求都不会发送到网页所在服务器;
当强缓存没有命中的时候,浏览器一定会发送一个请求到服务器,通过服务器端依据资源的另外以一些 http header
验证这个资源是否命中协商缓存
,如果命中协商缓存,服务器会将这个请求返回,但是不会返回这个资源的数据,而是告诉客户端直接从缓存中加载这个资源,于是浏览器就会又去从自己的缓存中去加载资源
。
强缓存与协商缓存的共同点是:如果命中,都是从客户端缓存中加载资源,而不是从服务器加载资源数据;区别是:强缓存不发请求到服务器,协商缓存会发请求到服务器。
强缓存
当某个浏览器请求的某个资源命中强缓存的时候,返回的http
状态码是200
,在chrome
的开发者工具的network
里面 size
会显示为from disk cache
。
强缓存是利用
Expires
或者Cache-Control
这两个http response header
实现的,它们都用来表示资源在客户端缓存的有效期。
Expires
Expires
是HTTP 1.0
提出的一个表示资源过期时间的header
,它描述的是一个绝对时间
,由服务器返回,用GMT
格式的字符串表示,如:Expires:Thu, 31 Dec 2037 23:55:55 GMT
,包含了Expires
头标签的文件,就说明浏览器对于该文件缓存具有非常大的控制权。
例如,一个文件的Expires
值是2020年的1月1日
,那么就代表,在2020年1月1日之前
,浏览器都可以直接使用该文件的本地缓存文件,而不必去服务器再次请求该文件,哪怕服务器文件发生了变化。
所以,Expires是优化中最理想的情况,因为它根本不会产生请求,所以后端也就无需考虑查询快慢
。
Expires
是较老的强缓存管理header
,由于它是服务器返回的一个绝对时间
,在服务器时间与客户端时间相差较大时,缓存管理容易出现问题,比如:随意修改下客户端时间,就能影响缓存命中的结果
。
所以在
HTTP 1.1
的时候,提出了一个新的header
,就是Cache-Control
。
Cache-Control
Cache-Control
描述的是一个相对时间,在进行缓存命中的时候,都是利用客户端时间进行判断,所以相比较Expires
,Cache-Control
的缓存管理更有效,安全一些。
这是一个相对时间,在配置缓存的时候,以秒
为单位,用数值表示,如:Cache-Control:max-age=315360000
。
此外,还可以为Cache-Control
指定 public
或 private
标记。如果使用private
,则表示该资源仅仅属于发出请求的最终用户,这将禁止中间服务器
(如代理服务器)缓存此类资源。对于包含用户个人信息的文件(如一个包含用户名的 HTML 文档),可以设置private
,一方面由于这些缓存对其他用户来说没有任何意义,另一方面用户可能不希望相关文件储存在不受信任的服务器上。
需要指出的是,private
并不会使得缓存更加安全,它同样会传给中间服务器(如果网站对于传输的安全性要求很高,应该使用传输层安全措施)。
对于public
,则允许所有服务器缓存该资源。通常情况下,对于所有人都可以访问的资源(例如网站的 logo、图片、脚本等),Cache-Control
默认设为 public
是合理的。
Cache-Control优先级高于Expires
- 这两个
header
可以只启用一个,也可以同时启用,当response header
中,Expires
和Cache-Control
同时存在时,Cache-Control
优先级高于Expires
。 -
Cache-Control
描述的是一个相对时间,在进行缓存命中的时候,都是利用客户端时间进行判断,所以相比较Expires
,Cache-Control
的缓存管理更有效,安全一些。 - 这两个
header
可以只启用一个,也可以同时启用,当response header
中,Expires
和Cache-Control
同时存在时,Cache-Control 优先级高于Expires
。
协商缓存
当浏览器对某个资源的请求没有命中强缓存,就会发一个请求到服务器,验证协商缓存是否命中,如果协商缓存命中,请求响应返回的http
状态为304
并且会显示一个Not Modified
的字符串,比如你打开京东的首页,按 f12 打开开发者工具,再按 f5 刷新页面,查看 network,可以看到有不少请求就是命中了协商缓存的。
Last-Modified If-Modified-Since
浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在response
的header
加上Last-Modified
的header
,这个header表示这个资源在服务器上的最后修改时间
;
浏览器再次跟服务器请求这个资源时,在request
的header
上加上If-Modified-Since
的header
,这个header的值就是上一次请求时返回的Last-Modified的值
;
根据浏览器传过来If-Modified-Since
和资源在服务器上的最后修改时间判断资源是否有变化,如果没有变化则返回304 Not Modified
,但是不会返回资源内容;如果有变化,就正常返回资源内容。当服务器返回304 Not Modified的响应时,response header 中不会再添加 Last-Modified 的 header,因为既然资源没有变化,那么 Last-Modified 也就不会改变
。
浏览器收到304
的响应后,就会从缓存中加载资源;
如果协商缓存没有命中,浏览器直接从服务器加载资源时,Last-Modified Header
在重新加载的时候会被更新,下次请求时,If-Modified-Since
会启用上次返回的Last-Modified
值。
Etag
【Last-Modified,If-Modified-Since】
都是根据服务器时间返回的header
,一般来说,在没有调整服务器时间和篡改客户端缓存的情况下,这两个header
配合起来管理协商缓存是非常可靠的。
但是有时候也会服务器上资源其实有变化,但是最后修改时间却没有变化的情况,而这种问题又很不容易被定位出来,而当这种情况出现的时候,就会影响协商缓存的可靠性
。
所以就有了另外一对header
来管理协商缓存,这对header
就是ETag
、If-None-Match
。它们的缓存管理的方式是:
Etag 的工作原理
浏览器第一次跟服务器请求一个资源,服务器在返回这个资源的同时,在response
的header
加上ETag
的header
,这个header
是服务器根据当前请求的资源生成的一个唯一标识,这个唯一标识是一个字符串,只要资源有变化这个串就不同,跟最后修改时间没有关系,所以能很好的补充Last-Modified的
问题;
浏览器再次跟服务器请求这个资源时,在request
的header
上加上If-None-Match
的header
,这个header
的值就是上一次请求时返回的ETag
的值;
服务器再次收到资源请求时,根据浏览器传过来If-None-Match
和然后再根据资源生成一个新的ETag
,如果这两个值相同就说明资源没有变化,否则就是有变化;如果没有变化则返回304 Not Modified
,但是不会返回资源内容。
如果有变化,就正常返回资源内容。与Last-Modified
不一样的是,当服务器返回304 Not Modified
的响应时,由于ETag
重新生成过,response header
中还会把这个ETag
返回,即使这个ETag
跟之前的没有变化;
浏览器收到304
的响应后,就会从缓存中加载资源。
Etag 和 Last-Modified
Etag
和Last-Modified
非常相似,都是用来判断一个参数,从而决定是否启用缓存。但是ETag
相对于Last-Modified
也有其优势,可以更加准确的判断文件内容是否被修改,从而在实际操作中实用程度也更高。
协商缓存跟强缓存不一样,强缓存不发请求到服务器,所以有时候资源更新了浏览器还不知道,但是协商缓存会发请求到服务器,所以资源是否更新,服务器肯定知道。大部分 web 服务器都默认开启协商缓存,而且是同时启用 Last-Modified,If-Modified-Since 和 ETag、If-None-Match
。
如果没有协商缓存,每个到服务器的请求,就都得返回资源内容,这样服务器的性能会极差。
Last-Modified
,If-Modified-Since
和 ETag
、If-None-Match
一般都是同时启用,这是为了处理Last-Modified
不可靠的情况。有一种场景需要注意:
协商缓存需要配合强缓存使用,
Last-Modified
这个header
,还有强缓存的相关header
,因为如果不启用强缓存的话,协商缓存根本没有意义。