学习链接
浏览器缓存(强缓存和协商缓存)
这里说的缓存是指浏览器(客户端)在本地磁盘中对访问过的资源保存的副本文件。
浏览器缓存主要有以下几个优点:
- 减少重复数据请求,避免通过网络再次加载资源,节省流量。
- 降低服务器的压力,提升网站性能。
- 加快客户端加载网页的速度, 提升用户体验。
浏览器缓存分为强缓存和协商缓存,区别:
如果浏览器命中强缓存,则不需要给服务器发请求;
而协商缓存会发送请求到服务器,并最终由服务器来决定是否使用缓存。在
Chrome
中强缓存(虽然没有发出真实的http
请求)的请求状态码返回是200 (from cache)
;
而协商缓存如果命中走缓存的话,请求的状态码是304 (Not Modified)
。
不同浏览器的策略不同,在Fire Fox
中,from cache
状态码是 304.
from cache
会分为from disk cache(磁盘缓存)
和from memory cache(内存缓存)
从内存中获取最快,但是是 session 级别的缓存,关闭浏览器之后就没有了。
请求流程
浏览器在第一次请求后缓存资源,再次请求时,会进行下面两个步骤:
浏览器会获取该缓存资源的
header
中的信息,根据response header
中的Expires
和Cache-Control
来判断是否命中强缓存,如果命中则直接从缓存中获取资源。如果没有命中强缓存,浏览器就会发送请求到服务器,这次请求会带上
If-Modified-Since
或者If-None-Match
,它们的值分别是第一次请求返回Last-Modified
或者Etag
,由服务器来对比这一对字段来判断是否命中协商缓存。
如果命中,则服务器返回304
状态码,并且不会返回资源内容,浏览器会直接从缓存获取;
否则服务器最终会返回资源的实际内容,并更新header
中的相关缓存字段。
简要:
- 先去内存看,如果有,直接加载
- 如果内存没有,择取硬盘获取,如果有直接加载
- 如果硬盘也没有,那么就进行网络请求
- 加载到的资源缓存到硬盘和内存
强缓存
强缓存是根据返回头中的 Expires
或者 Cache-Control
两个字段来控制的,都是表示资源的缓存有效时间。
Cache-Control
的优先级高于Expires
Expires
Expires
是 http 1.0
的规范,值是一个 GMT
格式的时间点字符串。
这个时间点代表资源失效的时间,如果当前的时间戳在这个时间之前,则判定命中缓存。
失效时间是一个绝对时间,如果服务器时间与客户端时间偏差较大时,就会导致缓存混乱。
Expires
受限于本地时间,如果修改了本地时间,可能会造成缓存失效
Cache-Control
Cache-Control
是 http 1.1
的规范,一般常用该字段的 max-age
值来进行判断。
表示的是相对时间,比如 Cache-Control:max-age=3600
代表资源的有效期是 3600 秒。
并且返回头中的 Date
表示消息发送的时间,表示当前资源在 Date ~ Date + 3600s
这段时间里都是有效的。
Cache-Control
还可以设置其他值:
no-cache
:不使用本地缓存(但可存数据到本地),需要通过协商缓存验证后才可使用
强制客户端直接向服务器发送请求,也就是说每次请求都必须向服务器发送。
这个很容易让人产生误解,使人误以为是响应不被缓存。实际上Cache-Control: no-cache
是会被缓存的,只不过每次在向客户端(浏览器)提供响应数据时,缓存都要向服务器评估缓存响应的有效性。no-store
:直接禁止浏览器缓存数据,每次请求资源都会向服务器要完整的资源public
:可以被所有用户缓存(多用户共享),包括终端用户和 CDN 等中间件代理服务器private
:只能被终端浏览器缓存(而且是私有缓存),不允许中继缓存服务器进行缓存
协商缓存
协商缓存是由服务器来确定缓存资源是否可用。主要涉及到两对属性字段,都是成对出现的。
即第一次请求的响应头带上 Last-Modified
或者 Etag
,则后续请求则会带上对应的请求字段 If-Modified-Since
或者 If-None-Match
;
若响应头没有 Last-Modified
或者 Etag
字段,则请求头也不会有对应的字段。
ETag
的优先级高于Last-Modified
Last-Modified,If-Modified-Since
Last-Modified
表示当前文件的最后修改时间,值为 GMT
格式的时间字符串。(http 1.0
)
下一次请求时,请求头中会带上 If-Modified-Since
(值为上次的 Last-Modified
),询问服务器在该日期后是否有更新,服务器根据文件的最后修改时间判断资源是否有变化。
如果文件没有变更则返回 304
状态码,浏览器直接使用本地缓存,否则返回新资源并更新 Last-Modefied
的值。
(服务器返回 304
时,响应头中不会返回 Last-Modified
)
ETag,If-None-Match
ETag
表示当前资源的唯一标识串,只要资源有变化就这个值就会改变,与最后修改时间无关。(http 1.1
)
下一次请求时,请求头中会带上 If-None-Match
(值为上次的 ETag
),询问服务器该资源是否有变化,服务器根据文件本身算出资源的唯一标识串来判断资源是否有变化。
服务器根据文件本身算出一个哈希值并通过 ETag
字段返回给浏览器,接收到 If-None-Match
字段以后,服务器通过比较两者是否一致来判定文件内容是否被改变。
如果文件没有变更则返回 304
状态码,浏览器直接使用本地缓存,否则返回新资源并更新 ETag
的值。
(服务器返回 304
时,响应头中会返回重新计算过的 ETag
,即使这个 ETag
跟之前的相同)
HTTP 中并没有指定如何生成 ETag,可以由开发者自行生成,哈希是比较理想的选择。
为什么要有 ETag
HTTP1.1
中 ETag
的出现主要是为了解决几个 Last-Modified
比较难解决的问题:
- 一些文件也许会周期性的更改,但是内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新 GET;
- 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说 1s 内修改了 N 次),
If-Modified-Since
能检查到的粒度是秒级的,使用Etag
就能够保证这种需求下客户端在1秒内能刷新 N 次 Cache。 - 某些服务器不能精确的得到文件的最后修改时间
优先级
Cache-Control > Expires > ETag > Last-Modified
缓存的选择
大部分 web 服务器都默认开启协商缓存,而且是同时启用Last-Modified/If-Modified-Since
和 ETag/If-None-Match
但是下面的场景需要注意:
- 分布式系统里多台机器间文件的
Last-Modified
必须保持一致,以免负载均衡到不同机器导致比对失败; - 分布式系统尽量关闭掉
ETag
(每台机器生成的ETag
都会不一样)