下面是经典的前端缓存知识点结构图:
首先需要知道,我们提到的 http 缓存指的是对 GET 请求的缓存,其它请求的缓存不在讨论之列。
其次,客户端向指定服务器发起的第一个请求肯定是没有缓存数据的,但是它们可以在第一次请求和响应中约定以后再次发起该请求时使用的缓存策略。总而言之就是,服务器在响应中告诉客户端是否需要以及用何种方法缓存数据,客户端自主判断能否使用本地缓存或者向服务器询问能否使用本地缓存。
☺ 为什么要使用 HTTP 缓存?
客户端与服务器之间通过网络连接:
- 加快了客户端访问网页的速度
- 缓解了服务器的压力
- 减少了冗余数据的传输,节省网费
☺ 如何使用 HTTP 缓存?
客户端一般需要缓存的资源有首页和其它静态资源。
首页缓存可在 index.html
的 meta
标签上设置:http-equiv
& content
。
禁用首页缓存:
<!-- ie -->
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="expires" content="0">
<!-- else -->
<meta http-equiv="cache-control" content="no-cache">
设置首页缓存:
<!-- ie -->
<meta http-equiv="expires" content="Mon, 20 Aug 2018 23:00:00 GMT">
<!-- else -->
<meta http-equiv="cache-control" content="max-age=60">
其它静态资源的缓存一般在服务器上设置,例如 Nginx,Apache 等。
缓存策略
缓存策略由服务器指定,客户端按照服务器指定的缓存策略对响应中的数据进行缓存。
下面是 HTTP 缓存中常用的四类缓存策略,另外 Pragma
属性是 HTTP 1.0 的遗留东西,现在已经不使用了。
Expires
在 HTTP 1.0 中定义,是最简单的缓存策略,告诉客户端缓存到哪个时间点过期。
服务器和客户端的时间不一致就会出问题。
缓存到期之前客户端不知道服务器资源是否发生更新。
总之,不推荐使用。
Cache-Control
在 HTTP 1.1 中定义,比 Expires
强一些,用有效时间长度代替有效时间点,避免了服务器和客户端时间不一致的问题,同时还添加了许多额外的选项。
同样的,缓存到期之前客户端不知道服务器资源是否发生更新。
其取值有:
no-cache
:客户端可以缓存数据,但是不能直接使用缓存数据,每次请求都需要向服务器询问,这个叫 新鲜度校验。由服务器决定是使用客户端缓存的资源(304)还是返回新的资源(200),这个过程实际上就是 协商缓存no-store
:客户端不缓存资源,每次请求都需要服务器返回最新的数据max-age=INT
:单位秒,告诉客户端缓存的有效时间,有效时间内使用强缓存,缓存失效使用协商缓private
/pubic
被单个用户 / 所有用户缓存,默认单用户must-revalidate
每次访问需要缓存校验
一般对于首页(index.html
)的资源加载应该使用每次请求+新鲜度校验的方法,即避免下文提到的强缓存,避免资源不同步刷新的问题。
设置 no-cache
就相当于设置 max-age=0; must-revalidate
,这个不难理解:客户端虽然缓存了数据,但是数据被标记了已经过期,配合 must-revalidate
让服务器进行协商缓存。
此外,除了响应头,请求头也可以设置 Cache-Control
属性,设为 no-cache
意味着客户端想告诉服务器它没有相关的缓存数据。
Last-Modified
服务器将 最后修改时间 返回给客户端,客户端下次请求时带上这个时间戳,服务器将这个时间与当前的最后修改时间进行比较,如果相同则返回 304,如果不同则返回 200 并附带上最新的数据和时间戳。
相比于 Expires
和 Cache-Control
,Last-Modified
避免了强缓存,不存在客户端和服务器版本不同步的问题。
但是这种策略仍然有许多缺点:
- 服务器是否返回 200 仅仅取决于 最后修改时间,这意味着,即使资源内容没有实质性的更新,而仅仅只是修改时间发生变化,也会触发 200,这显然是对服务器资源的一种浪费
- 记录精度只精确到秒(?应该是),如果资源在一秒内发生了实质性修改,那么服务器因为检测不到修改时间的变化而返回 304
- 服务器不能精确计算某些文件的修改时间,策略失效
客户端发请求时,在请求头上设置 If-Modified-Since=时间戳
或者 If-Unmodified-Since=时间戳
让服务器进行比较。
ETag
这种策略克服了 Last-Modified
的缺点,代价是更高的性能损耗。
ETag
需要服务器为资源计算一个哈希值,因此可以更加精确的判断资源是否修改,也不用担心一秒内发生多次修改而检测不到的情况了。
服务器会将客户端请求中的 ETag
值(上次请求使用的资源的哈希值)与当前时间资源的哈希值进行比较,决定返回 304 还是 200。
这个策略也有它的缺点,不过一般可能遇不到:在分布式服务器上,如果不同服务器计算 ETag
的算法不一样,就会导致客户端从一个服务器上获取的 ETag
发送到另一个服务器上时的比较没有啥意义。
客户端发请求时,在请求头上设置 If-Match=ETag值
或者 If-None-Match=ETag值
让服务器进行比较。
常用设置
告诉浏览器不读取缓存,由于客户端差异,最好设置三个属性:
Expires: -1;
Pragma: no-cache;
Cache-Control: no-cache;
数据仍然会被缓存,只是再次请求时需要询问服务器,即 协商缓存。
告诉浏览器先检查缓存,此时存在强缓存,缓存失效时才会协商缓存:
Expires: CURRENT_TIME_MILIS + 1000 * 60;
Cache-Control: max-age=60;
Pragma: 不知道怎么设置60s的语法;
HTTP 1.0 不支持 Cache-Control 策略,三种策略同时使用时,优先级从上到下升高。
强缓存
客户端发起请求之前,首先要根据缓存策略确定是否需要检查缓存,只有服务器同意使用浏览器使用缓存,才会有 强缓存 这一步。
响应头中与强缓存策略相关的属性有三个:Pragma、Cache-Control 和 Expires,这三个属性决定了浏览器是否读取缓存。
注意:浏览器请求时是否读取缓存,不等价于是否对服务器返回的数据进行缓存,我存了但可以不读。
在允许浏览器读取缓存的情况下,GET 请求将会优先检查浏览器缓存。
没有缓存就直接请求,并依据服务器的响应获取数据并设置缓存策略。
有的话就先看看缓存是否过期,缓存没有过期就直接用,此时浏览器不会向服务器发起新的请求,而是直接从本地缓存中拿数据,这叫做 强缓存。
判断缓存是否过期的策略也是服务器制定的,所以按照服务器约定的规则直接取用本地数据也是合情合理的。
强缓存的缺点就是,因为没有向服务器请求新的资源,如果在本地缓存有效期内,服务器资源发生了更新,那么本地是不能了解这个情况的。所以有时候服务器更新了,但是本地没有更新就可以考虑是不是强缓存导致的。
使用强缓存的请求与正常请求的响应状态码都是 200,但是不同的浏览器会对强缓存做出不同的标识:
- Chrome 会在 "Size" 那一列显示
from memory cache
或者from disk cache
- Firefox 会在 "传输" 那一列显示 "已缓存"
- ……
协商缓存
如果缓存数据过期,客户端就会按照预先约定的协商策略询问服务器是否可以使用本地的缓存数据。
在首次响应中,服务器会相关资源的标签(最后修改时间或者 ETag 值)返回给客户端,下次请求时客户端附带上这个资源标签,服务器通过判断资源的最新标签是否发生变化来判断资源是否发生更新,从而决定返回 304 还是200:
- 返回 304 表示资源未更新,服务器同意浏览器使用本地缓存
- 返回 200 表示资源有更新,服务器返回最新的资源和资源标签
跟协商缓存相关的 响应头 有 Last-Modified
和 ETag
,对应的 请求头 是 If-Modified-Since
和 If-None-Match
。
Last-Modified/If-Modified-Since
服务器在首次响应头中设置 Last-Modified
为资源的 最后修改时间,浏览器再次请求时在请求头中设置 If-Modified-Since
为该时间,服务器检查资源的最后修改时间是否发生变化:
- 如果有变化,说明资源有更新,返回最新的资源和资源的最后修改时间,状态码为 200
- 如果无变化,说明资源无更新,返回 304,告诉浏览器使用本地缓存
缺点是:
- 资源内容未更新而最后修改时间更新时,也会触发 200
- 极短时间内资源更新导致最后修改时间未更新,触发 304 导致浏览器不能获取到最新资源
- 部分资源不能精确的获取最后修改时间,策略失效
- 服务器与代理服务器时间不一致
ETag/If-None-Match
为了解决 Last-Modified 的第1,2个痛点,HTTP 1.1 中定义了 ETag 策略。服务器为资源计算一个哈希值,并在首次响应中存储在 ETag
属性上返回给浏览器,浏览器再次请求时在请求头中设置 If-None-Match
为该哈希值,服务器重新计算资源的哈希值并与请求中的哈希值进行比较:
- 如果有变化,说明资源有更新,返回最新的资源和新的哈希值,状态码为 200
- 如果无变化,说明资源无更新,返回 304,告诉浏览器使用本地缓存
缺点是:
-
每次请求都需要额外计算哈希值,性能损耗更大
-
在分布式服务器上,不同服务器如果使用不同的哈希策略,导致比较无意义
评论区