浏览器缓存机制
浏览器缓存主要有以下几个优点:
- 减少重复数据请求,避免通过网络再次加载资源,节省流量。
- 降低服务器的压力,提升网站性能。
- 加快客户端加载网页的速度, 提升用户体验。
浏览器缓存分为强缓存
和协商缓存
,两者有两个比较明显的区别:
- 如果浏览器命中强缓存,则不需要给服务器发请求;而协商缓存最终由服务器来决定是否使用缓存,即客户端与服务器之间存在一次通信。
- 在 chrome 中强缓存(虽然没有发出真实的 http 请求)的请求状态码返回是
200 (from cache)
;而协商缓存如果命中走缓存的话,请求的状态码是304 (not modified)
。 不同浏览器的策略不同,在 Fire Fox中,from cache
状态码是304
.
缓存位置
- Service Worker
- Memory Cache
- Disk Cache
- Push Cache
Memory Cache
(内存中的缓存):读取高效,但是持续性很短,一旦关闭 Tab 页面,内存中的缓存也就被释放了。
Disk Cache
(硬盘中的缓存):读取速度慢,容量和存储时效性上有优势,
请求流程
浏览器在第一次请求后缓存资源,再次请求时,会进行下面两个步骤:
- 浏览器会获取该缓存资源的
header
中的信息,根据response header
中的expires
和cache-control
来判断是否命中强缓存,如果命中则直接从缓存中获取资源。 - 如果没有命中强缓存,浏览器就会发送请求到服务器,这次请求会带上
IF-Modified-Since
或者IF-None-Match
, 它们的值分别是第一次请求返回Last-Modified
或者Etag
,由服务器来对比这一对字段来判断是否命中。如果命中,则服务器返回304状态码,并且不会返回资源内容,浏览器会直接从缓存获取;否则服务器最终会返回资源的实际内容,并更新header
中的相关缓存字段。
http缓存分为强制缓存和协商缓存
- 强缓存
强缓存是根据返回头中的 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
或者Etag
,则后续请求则会带上对应的请求字段If-Modified-Since
或者If-None-Match
,若响应头没有Last-Modified
或者Etag
字段,则请求头也不会有对应的字段。 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 > expires > Etag > Last-Modified
用户行为对缓存的影响
经过对qq、fire fox 、safari 、chrome 这几个浏览器的访问同一个页面测试我发现,不同的浏览器在 F5 刷新的时候 ,同一个文件 qq 、fire fox 浏览器会返回 304 Not Nodified,在请求头中不携带 Expires/Cache-Control
; 而 chrome 和 safari 刷新的时候,会返回 200 from cache
, 没有真正发起请求,走强缓存。可见不同的浏览器反馈是不一致的,所以下面表格中”F5刷新”时 Expires/Cache-Control
会无效我认为是存在一定争议的。
而 Ctrl + F5 强制刷新的时候,会暂时禁用强缓存和协商缓存。
用户操作 | Expires/Cache-Control | Last-Modied/Etag |
---|---|---|
地址栏回车 | 有效 | 有效 |
页面链接跳转 | 有效 | 有效 |
新开窗口 | 有效 | 有效 |
前进回退 | 有效 | 有效 |
F5刷新 | 无效(有争议,不同浏览器反馈不一致) | 有效 |
Ctrl+F5强制刷新 | 无效 | 无效 |
如何设置强缓存和协商缓存
- 后端服务器,写入代码逻辑中:
res.setHeader('max-age': '3600 public')
res.setHeader(etag: '5c20abbd-e2e8')
res.setHeader('last-modified': Mon, 24 Dec 2018 09:49:49 GMT)
- Nginx 配置
add_header Cache-Control "max-age=3600"
三级缓存原理
- 先去内存看,如果有,直接加载
- 如果内存没有,择取硬盘获取,如果有直接加载
- 如果硬盘也没有,那么就进行网络请求
- 加载到的资源缓存到硬盘和内存