一、浏览器缓存技术
浏览器中,为了加快一些不常变内容的访问速度,会将其缓存在本地中,当下次再次访问时,就直接加载这些资源,从而加速访问。这种技术称之为浏览器缓存。
判断浏览器加载的一个资源,是否是使用缓存,可以使用开发者工具查看Network,存在缓存中的资源,其Size
会显示为from disk cache
或者from memory cache
,如:
启用缓存后,至少会带来两点好处:
(1)减少页面等待时间
(2)减少服务器负载
而浏览器缓存的启用和如何缓存,这个行为是 由服务器控制的,也就是说服务端返回的响应报文中的响应头
,决定了如何进行浏览器缓存,在HTTP协议中,和浏览器缓存有关的字段有:
- 通用首部字段
Cache-Control
控制缓存的行为Pragma
当值为no-cache
时,禁用缓存
- 请求首部字段
If-Match
比较Etag是否一致If-None-Match
比较Etag是否不一致If-Modified-Since
比较资源最后更新的时间是否一致If-Unmodified-Since
比较资源最后更新的时间是否不一致
- 响应首部字段
Etag
资源的匹配信息
- 实体首部字段
Expires
实体主体过期的时间Last-Modified
资源最后一次修改的时间
二、强缓存与协商缓存
浏览器缓存按缓存策略的不同,可以分为强缓存
与协商缓存
。他们的概念是:
1)浏览器根据资源的HTTP Header判断资源是否命中强缓存,如果命中,则 直接从缓存中读取资源,连请求都不会发送到服务器
2)如果未命中强缓存,则浏览器会发送资源请求到服务器,根据资源的HTTP Header判断是否命中协商缓存,如果命中协商缓存,则服务器会将请求返回,但是 不返回资源的数据,而是告诉客户端可以从缓存中加载资源,从而让客户端自行从缓存中加载资源
也就是说,强缓存
与协商缓存
的不同垫在于,协商缓存会发送请求到服务器判断是否命中缓存,而它们的共同点则在于一旦命中缓存,便都从客户端缓存中加载资源。如果协商缓存也没有命中,则使用服务端返回的资源数据
1、强缓存:Expires和Cache-Control
当某个资源命中强缓存时,其返回的HTTP状态为200,Network里的Size
中会显示为from cache
,强缓存是通过HTTP Header的Expires
和Cache-Control
控制的。
其中,Expires
表示资源过期的绝对时间,以GMT格式字符串表示,如:Mon, 28 Aug 2028 16:37:42 GMT
,浏览器处理该字段的过程如下:
1)浏览器第一次请求服务器某个资源,服务器响应并返回该资源,响应头中会带上Expires
字段
2)浏览器缓存下该资源和 其响应头,故缓存命中的请求中的header是来自于之前缓存的header
3)当浏览器再次请求该资源时,就先从缓存中查找,若找到资源,就比较Expires
和当前请求时间,若currentTime < Expires
,返回资源,否则请求服务器加载资源并更新缓存
不过Expires
是比较老的强缓存管理,服务器返回的是绝对的时间,故如果客户端与服务端时间上不同步,缓存管理就可能出现问题(如随便修改下客户端时间,就能够影响缓存有效与否的判断)。故HTTP/1.1里引入了Cache-Control
,当Cache-Control
和Expires
同时存在时,优先级为Cache-Control
更高。不过Expires
是比较老的强缓存管理,服务器返回的是绝对的时间,故如果客户端与服务端时间上不同步,缓存管理就可能出现问题(如随便修改下客户端时间,就能够影响缓存有效与否的判断)。故HTTP/1.1里引入了Cache-Control
,当Cache-Control
和Expires
同时存在时,优先级为Cache-Control
更高。
然而Cache-Control
并不是响应头的专利,实际上request和response里都可以有Cache-Control
字段,但request的Cache-Control
字段,则主要是提供一套 客户端主动控制缓存代理服务器的缓存策略 方案,但这就不属于浏览器缓存的范畴了,所以我们这里讨论的Cache-Control
,则主要是从响应头角度来讨论。Cache-Control
的常用取值如下:
max-age=[秒]
,表示指定[秒]
内,缓存有效,客户端可以直接使用缓存no-cache
,告诉浏览器、缓存服务器,不管本地副本是否过期,使用副本之前,一定要到源服务器进行副本有效性校验no-store
,真正的不进行缓存,每次必须重新请求服务器must-revalidate
,告诉浏览器、缓存服务器,本地副本过期之前可以使用本地副本;本地副本一旦过期,则必须去源服务器进行有效性校验public
,表示响应可被任何中间人(如:中间代理、CDN)缓存private
,表示响应是专于某单个用户的,中间人不能缓存此响应,只能应用于浏览器的私有缓存中
2、协商缓存:Last-Modified和Etag
当某个资源未命中强缓存时,就会发一个请求到服务器验证协商缓存是否命中。协商缓存如果命中,就会返回304 Not Modified
的HTTP响应报文,然后浏览器便从本地加载缓存。
与协商缓存有关的方式有两种,即为Last-Modified
和Etag
。
Last-Modified控制缓存过程
1)浏览器第一次跟服务器请求某个资源,服务器在返回资源的同时,头部还带上Last-Modified
,表示该资源在服务器上的最后修改时间
2)浏览器再次请求资源时,在请求头里带上If-Modified-Since
,值为上一次请求返回的Last-Modified
值
3)服务端根据浏览器传过来的If-Modified-Since
和资源在服务器上最后的修改时间 判断资源是否有变化,如果没有变化则返回304 Not Modified
,并且304报文的头部里不会加上Last-Modified
。如果有变化,那么返回新的资源,并且头部里带上Last-Modified
4)对于304的响应,浏览器就从缓存中加载资源
相比Expires
,虽然使用Last-Modified
的协商缓存也是采用时间对比形式,但是Last-Modified
全程使用的是服务端时间,而非客户端时间,故要可靠一些。但是有时候也可能出现:服务端上的资源其实有变化,但是最后修改时间却没有变化的情况,也就是缓存不够精准的情况。为了解决这种问题,出现了ETag
和If-None-Match
这对CP。
ETag控制缓存过程
1)浏览器第一次跟服务器请求某个资源,服务器在返回这个资源的同时,头部会带上ETag
,ETag的值是根据当前请求资源生成的唯一标识,这个唯一标识是一个字符串,资源一旦变化这个值就会发生变更,与最后修改时间无关。所以可以很好地补充Last-Modified
的问题
2)浏览器再次请求资源时,在请求头里带上If-None-Match
,值为上次请求返回的ETag
值
3)服务端接收浏览器传过来的If-None-Match
,并且计算请求资源的ETag值,然后进行对比,当ETag对比一样时,表示资源未修改,返回304 Not Modified
,且不会返回资源内容,不过,仍然会返回新计算的ETag值
。如果对比不一样,则返回新的资源。
4)浏览器收到304响应后,从缓存中加载资源
很多web服务器都默认开启Last-Modified/If-Modified-Since
和ETag/If-None-Match
,并且是同时启用的。不过分布式系统里面,则有一些不同,这是因为:分布式系统里多台机器的Last-Modified
必须保持一致,避免负载均衡到不同机器导致对比失败;分布式系统尽量关闭ETag,因为每台机器生成的ETag都会不一样
注意:浏览器并不会处理ETag
和Last-Modified
的优先级,它只会保存下它们,并且在下次请求时相应地带上If-None-Match
或者If-Modified-Since
,优先级的处理则交由服务端处理。一般来说,ETag
方案被认为是强验证器,而Last-Modified
则被认为是弱的。