浏览器缓存
前段时间遇到了关于浏览器缓存的问题,当时没有什么想法,后来回来查了一下发现很多东西都接触过,只是没有系统地整理起来,借着这篇博客整理一下。
主要内容是浏览器的强缓存,协商缓存的机制以及对应的HTTP头。
另外以后可能会整理下浏览器的Cache API相关的内容。
强缓存和协商缓存
浏览器缓存分为强缓存和协商缓存,两者有两个比较明显的区别:
- 如果浏览器命中强缓存,则不需要给服务器发请求;而协商缓存最终由服务器来决定是否使用缓存,即客户端与服务器之间存在一次通信。
- 在
chrome
中强缓存(虽然没有发出真实的http
请求)的请求状态码返回是200 (from cache)
;而协商缓存如果命中走缓存的话,请求的状态码是304 (not modified)
。 不同浏览器的策略不同,在Fire Fox
中,from cache
状态码是 304.
其中 from cache 会分为 from disk cache 和 from memory cache. 从内存中获取最快,但是是 session 级别的缓存,关闭浏览器之后就没有了。
缓存机制
首先我们来总体感知一下它的匹配流程,如下:
- 浏览器发送请求前,根据请求头的expires和cache-control判断是否命中(包括是否过期)强缓存策略,如果命中,直接从缓存获取资源,并不会发送请求。如果没有命中,则进入下一步。
- 没有命中强缓存规则,浏览器会发送请求,根据请求头的last-modified和etag判断是否命中协商缓存,如果命中,直接从缓存获取资源。如果没有命中,则进入下一步。
- 如果前两步都没有命中,则直接从服务端获取资源。
请求流程
浏览器在第一次请求后缓存资源,再次请求时,会进行下面两个步骤:
- 浏览器会获取该缓存资源的
header
中的信息,根据response header
中的expires
和cache-control
来判断是否命中强缓存,如果命中则直接从缓存中获取资源。 - 如果没有命中强缓存,浏览器就会发送请求到服务器,这次请求会带上
IF-Modified-Since
或者IF-None-Match
, 它们的值分别是第一次请求返回Last-Modified
或者Etag
,由服务器来对比这一对字段来判断是否命中。如果命中,则服务器返回 304 状态码,并且不会返回资源内容,浏览器会直接从缓存获取;否则服务器最终会返回资源的实际内容,并更新 header 中的相关缓存字段。
强缓存
强缓存是根据返回头中的 Expires
或者 Cache-Control
两个字段来控制的,都是表示资源的缓存有效时间。
Expires
是http 1.0
的规范,值是一个GMT
格式的时间点字符串,比如Expires:Mon,18 Oct 2066 23:59:59 GMT
。这个时间点代表资源失效的时间,如果当前的时间戳在这个时间之前,则判定命中缓存。有一个缺点是,失效时间是一个绝对时间,如果服务器时间与客户端时间偏差较大时,就会导致缓存混乱。而服务器的时间跟用户的实际时间是不一样是很正常的,所以Expires
在实际使用中会带来一些麻烦。Cache-Control
这个字段是http 1.1
的规范,一般常用该字段的max-age
值来进行判断,它是一个相对时间,比如 .Cache-Control:max-age=3600
代表资源的有效期是 3600 秒。并且返回头中的Date
表示消息发送的时间,表示当前资源在Date ~ Date +3600s
这段时间里都是有效的。不过我在实际使用中常常遇到设置了max-age
之后,在max-age
时间内重新访问资源却会返回304 not modified
,这是由于服务器的时间与本地的时间不同造成的。当然Cache-Control
还有其他几个值可以设置, 不过相对来说都很少用了:no-cache
不使用本地缓存。需要使用协商缓存。no-store
直接禁止浏览器缓存数据,每次请求资源都会向服务器要完整的资源, 类似于network
中的disabled cache
。public
可以被所有用户缓存,包括终端用户和 cdn 等中间件代理服务器。private
只能被终端用户的浏览器缓存。
如果 Cache-Control
与 Expires
同时存在的话, Cache-Control
的优先级高于 Expires
。
协商缓存
协商缓存是由服务器来确定缓存资源是否可用。 主要涉及到两对属性字段,都是成对出现的,即第一次请求的响应头带上某个字, Last-Modified
或者 Etag
,则后续请求则会带上对应的请求字段 If-Modified-Since
或者 If-None-Match
,若响应头没有 Last-Modified
或者 Etag
字段,则请求头也不会有对应的字段。
Last-Modified/If-Modified-Since
二者的值都是 GMT 格式的时间字符串,Last-Modified
标记最后文件修改时间, 下一次请求时,请求头中会带上If-Modified-Since
值就是Last-Modified
告诉服务器我本地缓存的文件最后修改的时间,在服务器上根据文件的最后修改时间判断资源是否有变化, 如果文件没有变更则返回304 Not Modified
,请求不会返回资源内容,浏览器直接使用本地缓存。当服务器返回304 Not Modified
的响应时,response header
中不会再添加的Last-Modified
去试图更新本地缓存的Last-Modified
, 因为既然资源没有变化,那么Last-Modified
也就不会改变;如果资源有变化,就正常返回返回资源内容,新的Last-Modified
会在response header
返回,并在下次请求之前更新本地缓存的Last-Modified
,下次请求时,If-Modified-Since
会启用更新后的Last-Modified
。Etag/If-None-Match
, 值都是由服务器为每一个资源生成的唯一标识串,只要资源有变化就这个值就会改变。服务器根据文件本身算出一个哈希值并通过ETag
字段返回给浏览器,接收到If-None-Match
字段以后,服务器通过比较两者是否一致来判定文件内容是否被改变。与Last-Modified
不一样的是,当服务器返回304 Not Modified
的响应时,由于在服务器上ETag
重新计算过,response header
中还会把这个ETag
返回,即使这个ETag
跟之前的没有变化。
HTTP 中并没有指定如何生成 ETag,可以由开发者自行生成,哈希是比较理想的选择。
为什么要有 Etag
HTTP1.1
中 Etag
的出现主要是为了解决几个 Last-Modified
比较难解决的问题:
- 一些文件也许会周期性的更改,但是内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新 GET;
- 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说 1s 内修改了 N 次),
If-Modified-Since
能检查到的粒度是秒级的,使用Etag
就能够保证这种需求下客户端在 1 秒内能刷新 N 次 cache。 - 某些服务器不能精确的得到文件的最后修改时间。
Cache-control
可缓存性
public
表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存,即使是通常不可缓存的内容。(例如:1.该响应没有
max-age
指令或Expires
消息头;2. 该响应对应的请求方法是 POST 。)private
表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。私有缓存可以缓存响应内容,比如:对应用户的本地浏览器。
no-cache
在发布缓存副本之前,强制要求缓存把请求提交给原始服务器进行验证(协商缓存验证)。
no-store
缓存不应存储有关客户端请求或服务器响应的任何内容,即不使用任何缓存。
到期
max-age=<seconds>
设置缓存存储的最大周期,超过这个时间缓存被认为过期(单位秒)。与
Expires
相反,时间是相对于请求的时间。s-maxage=<seconds>
覆盖
max-age
或者Expires
头,但是仅适用于共享缓存(比如各个代理),私有缓存会忽略它。max-stale[=<seconds>]
表明客户端愿意接收一个已经过期的资源。可以设置一个可选的秒数,表示响应不能已经过时超过该给定的时间。
min-fresh=<seconds>
表示客户端希望获取一个能在指定的秒数内保持其最新状态的响应。
stale-while-revalidate=<seconds>
表明客户端愿意接受陈旧的响应,同时在后台异步检查新的响应。秒值指示客户愿意接受陈旧响应的时间长度。
stale-if-error=<seconds>
表示如果新的检查失败,则客户愿意接受陈旧的响应。秒数值表示客户在初始到期后愿意接受陈旧响应的时间。
重新验证和重新加载
must-revalidate
一旦资源过期(比如已经超过
max-age
),在成功向原始服务器验证之前,缓存不能用该资源响应后续请求。proxy-revalidate
与must-revalidate作用相同,但它仅适用于共享缓存(例如代理),并被私有缓存忽略。
immutable
表示响应正文不会随时间而改变。资源(如果未过期)在服务器上不发生改变,因此客户端不应发送重新验证请求头(例如
If-None-Match
或If-Modified-Since
)来检查更新,即使用户显式地刷新页面。在Firefox中,immutable只能被用在https://
transactions. 有关更多信息,请参阅这里。
其他
no-transform
不得对资源进行转换或转变。
Content-Encoding
、Content-Range
、Content-Type
等HTTP头不能由代理修改。例如,非透明代理或者如Google’s Light Mode可能对图像格式进行转换,以便节省缓存空间或者减少缓慢链路上的流量。no-transform
指令不允许这样做。only-if-cached
表明客户端只接受已缓存的响应,并且不要向原始服务器检查是否有更新的拷贝。
优先级
1 | Cache-Control > expires > Etag > Last-Modified |
三级缓存原理
最后总结一下浏览器的三级缓存原理:
- 先去内存看,如果有,直接加载
- 如果内存没有,去硬盘获取,如果有直接加载
- 如果硬盘也没有,那么就进行网络请求
- 加载到的资源缓存到硬盘和内存