浏览器缓存
什么是浏览器缓存
我们都知道,web
页面显示所需要的各种资源文件(html
,css
,js
,png
等)都是通过 HTTP
请求从服务端获取的。浏览器缓存便是将这些资源文件保存在浏览器(内存或者硬盘)中,这样同样的资源文件在后续请求中,如果命中缓存,则直接读取浏览器缓存,而无需重新从服务端获取。
浏览器会在内存、硬盘中开辟一个空间用于保存请求资源副本。我们经常调试时在Chrome
的 DevTools Network
里看到 Memory Cache
(內存缓存)和 Disk Cache
(硬盘缓存),指的就是缓存所在的位置。缓存在内存当中,响应速度更快。但是对于大文件,大概率存储在硬盘中;当系统内存使用率高的话,文件优先存储进硬盘。
为什么需要浏览器缓存
HTTP
请求耗时耗流量,高频的访问服务器也会对服务器形成压力。而且 HTTP
请求本身就是浏览器的宝贵资源,并行请求的数量是有限的。所以使用缓存有以下几个好处:
- 减少了无谓的流量消耗和浏览器资源消耗。
- 增加了网页的响应速度,提升了用户体验。
- 降低了服务器的压力。
为什么需要缓存策略?
- 有些资源文件是高频更新的,不需要也不能被缓存。
- 有些资源文件是定期更新的,希望被缓存,但是缓存需要有时效性。时效范围内使用缓存,过了失效则从服务器获取。
- 有些资源文件是不定期更新的,希望被缓存,但是更新后需要重新从服务器获取并更新缓存,不能命中旧缓存。
为了满足以上几点,浏览器就需要一套浏览器缓存策略来保证。
浏览器缓存策略
存储策略:请求后,用于判断是否缓存/更新资源。浏览器会根据服务器响应的报文信息(
Response Headers
)判断是否缓存/更新该资源。- 缓存资源发生在首次请求后。
- 更新资源发生在后续请求后。
命中策略:请求中,用于判断是否使用缓存资源。命中策略的前提是浏览器检索到当前请求资源存在本地缓存。
- 不存在本地缓存: 直接发起
HTTP
请求,从服务器获取资源文件。 - 存在本地缓存:根据
HTTP
请求头中的缓存标识
判断是否使用缓存,因为缓存可能已过期。
- 不存在本地缓存: 直接发起
根据尝试命中缓存的顺序,可以将 命中策略 分为两个阶段:
强缓存
命中阶段: 浏览器每次发起请求时,首先尝试命中强缓存
。- 如果命中,浏览器会直接读取本地缓存直接响应,不会向服务器发起
HTTP
请求,请求状态是200
。 - 如果没有命中,会向服务器发起
HTTP
请求,进入 协商缓存 阶段。
- 如果命中,浏览器会直接读取本地缓存直接响应,不会向服务器发起
协商缓存
命中阶段:强缓存
如果未命中,浏览器会携带参数向服务器发起完整的HTTP
请求。服务器校验是否命中协商缓存
。- 如果命中,服务器返回
304
状态码,通知浏览器直接使用本地缓存资源。 - 如果没有命中,将从服务端获取资源并返回(状态码
200
)。
- 如果命中,服务器返回
下面,让我们分别详细的了解一下两个阶段具体的 命中策略 和对应的 缓存标识。
强缓存命中策略
控制 强缓存 命中的 缓存标识 有两个:
Expires
:Http1.0
就存在,是一个绝对时间。用以表达在这个时间点之前发起请求可以直接从浏览器中读取数据,而无需发起请求。Cache-Control
:Http1.1
新增标识。为了解决Expires
在浏览器时间被手动更改导致缓存判断错误的问题。优先级更高。
强缓存 - Expires
来源:来自
HTTP
请求的服务器响应消息(Response Headers
)。用法:表示 缓存到期时间,是一个绝对的时间 (当前时间+缓存时间)。浏览器在 缓存到期时间 之前再次请求资源即命中,直接使用本地缓存。
优势:
Http 1.0
产物,同时兼容Http 1.0
和Http 1.1
。简单易用。劣势
- 时间是由服务器发送的(
UTC
),如果服务器时间和客户端时间存在不一致,可能会出现问题。 - 用户可能会将客户端本地的时间进行修改,而导致浏览器判断缓存失效。
- 时间是由服务器发送的(
强缓存 - Cache-control
来源:
HTTP 1.1
新增,同样来自HTTP
请求的服务器响应消息(Response Headers
)。用法:提供了一系列更细致的功能设置。常用值如下:
private
: 默认值。资源只可以被客户端缓存(代理服务器不能);后续请求都会直接命中强缓存。public
: 资源可以被缓存 (包括客户端和代理服务器,如 CDN边缘缓存服务器);后续请求都会直接命中强缓存。no-cache
: 资源可以被缓存;后续请求不命中 强缓存,但是可以尝试命中协商缓存。no-store
: 真正意义上的 “不要缓存”。资源根本不会被缓存,所以也不会执行命中策略,直接请求服务器获取资源。max-age=<seconds>
: 缓存存储的最长周期,周期内再次请求都会直接命中强缓存,超过这个周期本地缓存会过期失效。other
: 更多高级用法,参考MDN
优势:
HTTP 1.1
产物,以时间间隔标识失效时间,解决了Expires
服务器和客户端相对时间的问题。- 相比
Expires
,提供了更多选项设置。
劣势:同样存在客户端本地时间被修改,缓存有效期失真的问题。
Cache-control
的优先级高于Expires
,为了兼容HTTP/1.0
和HTTP/1.1
,实际项目中两个字段都可以设置。
协商缓存命中策略
进入协商缓存命中阶段有 两个 前提条件:
- 请求的资源文件,已经在本地缓存当中。
- 强缓存没有命中。
这个时候浏览器会向服务器发起完整的 HTTP
请求,服务器会通过请求头中的 缓存标识
来判断是否命中协商缓存。
决定协商缓存是否命中的 缓存标识
有两组:
Last-Modified
和If-Modified-since
: 两者都是一个(ETC
)时间,表示服务器资源最后一次修改的时间。Http1.0
就有。Last-Modified
:服务器会放 响应报文头Response Header
响应,浏览器会保存/更新该信息。If-Modified-since
:浏览器会放 请求报文头Request Header
携带。
Etag
和If-None-match
: 表示的是服务器资源的唯一标识,只要资源变化,Etag
就会重新生成。Http1.1
新增Etag
: 服务器会放响应报文头Response Header
响应,浏览器会保存/更新该信息。If-None-match
: 浏览器会放 请求报文头Request Header
携带。
Etag/If-None-match
的优先级比 Last-Modified/If-Modified-since
高。
协商缓存 - Last-Modified/If-Modified-since
- 服务器通过
Last-Modified
字段告知客户端,资源最后一次被修改的时间,例如Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT
- 浏览器将这个值和文件一起记录在缓存数据库中。
- 下一次请求相同资源时时,浏览器在请求头中将上次的
Last-Modified
的值写入到请求头的If-Modified-Since
字段。 - 服务器会将
If-Modified-Since
的值与Last-Modified
值进行对比。如果相等,则表示未修改,响应304
;反之,则表示修改了,响应200
状态码,并返回数据。
优点:
不存在版本问题,每次请求都会去服务器进行校验。服务器对比最后修改时间如果相同则返回304,不同返回200以及资源内容。
缺点:
- 只要资源修改,无论内容是否发生实质性的变化,都会将该资源返回客户端。例如周期性重写,这种情况下该资源包含的数据实际上一样的。
- 以时刻作为标识,无法识别一秒内进行多次修改的情况。 如果资源更新的速度是秒以下单位,那么该缓存是不能被使用的,因为它的时间单位最低是秒。
- 某些服务器不能精确的得到文件的最后修改时间。
- 如果文件是通过服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能没有变化,所以起不到缓存的作用。
协商缓存-Etag/If-None-match
为了解决上述问题,Http1.1
新增了一组字段 Etag
和 If-None-Match
。
- 服务器存储着文件的
Etag
字段。Etag
存储的是文件的唯一标识(一般都是hash
生成的),只有文件内容的变化才会导致Etag
更改。 - 浏览器在发起请求时,服务器在 Response header中返回
Etag
字段。 - 浏览器会将Etag值和文件一起记录在缓存数据库中。
- 在下一次请求相同资源时,浏览器会将上一次返回的
Etag
值赋值给If-No-Matched
并添加在Request Header
中。 - 服务器将浏览器传来的
If-No-Matched
跟自己本地资源的Etag
做对比。如果匹配,则返回304
通知浏览器读取本地缓存,否则返回200
和 更新后的资源。
优点:
- 可以更加精确的判断资源是否被修改,可以识别一秒内多次修改的情况。
- 不存在版本问题,每次请求都回去服务器进行校验。
缺点:
- 计算
Etag
值需要性能损耗。 - 分布式服务器存储的情况下,计算
Etag
的算法如果不一样,会导致浏览器从一台服务器上获得页面内容后到另外一台服务器上进行验证时现Etag
不匹配的情况。