彻底搞懂 Http 缓存策略,切记死背概念!

3,697 阅读9分钟

今天,我们就来说一说http的缓存策略:

在web中,http请求一般都是浏览器发起的,所以我们这里所说的http的缓存策略,其实也就是浏览器端的缓存策略,因为http本身只是一种协议,真正实现缓存还是要靠浏览器(其实就是浏览器指定存储在硬盘下。)

我们先自己想一想,浏览器如何想要缓存数据该如何实现呢?

可能有的小伙伴就会直接想到,直接将http请求到的数据,存储到localstory中不就可以啦,是的,本质上最核心的就是这样,那为什么我们所遇到的浏览器缓存问题就那么复杂呢?

原因就在于要想实现一个完整的缓存,我们需要考虑很多实际因素,例如:

  1. 我们设置完缓存以后,之后的数据总不能一直从缓存中读吧,因为我们请求的资源可能是会随时变化的,所以是不是需要给定一个策略,去告诉浏览器什么时候读缓存的数据,什么时候又重新请求服务器端的数据呢?答案当然是需要的,这也就是响应头返回的Expires,Cache-Control等字段的作用,即给一个缓存的失效时间不就可以啦。
  2. 光给一个缓存的失效时间就可以了吗?例如我们发送了一个请求,服务器端告诉浏览器端缓存的失效时间是10s中,那我们在10s之后重新请求,如果这个请求返回的数据根本没有变化,其实我们是不是没必要重新请求呀,直接读缓存不就可以啦,所以呢?我们还需要判断服务器端的资源是否发生了变化,那如何判断呢?这就是Last-Modified和Etag的作用,前者是资源的最后修改时间,后者是资源的唯一标识,我们稍后会细讲。

相信通过上面的介绍,我们应该对如何实现缓存,有了一个大致的了解,知道了原来缓存需要考虑这么多因素,才可以最终覆盖到各种情况,从而能走缓存的时候,尽可能走缓存,不能走缓存的时候,也不能胡乱走缓存。

接下来,我们就来详细介绍一下浏览器的缓存策略。

http缓存策略

首先,我们要知道一点:http的缓存策略,是由客户端和服务器端共同去控制的,客户端可以通过在请求头里添加Cache-Control等字段来决定是否走缓存,服务器端也可以在响应头中添加Cache-Control等字段来告诉客户端是否可以缓存数据。

不管是客户端还是服务器端都是通过http头中的不同字段来控制的,

服务器端的缓存控制

接下来 我们来看一下服务器端的缓存策略,也就是说http响应头中的相关字段,我们先列出来,一个个来说:

  • Expires
  • Cache-Control
  • Last-Modified
  • Etag
  1. Expires

Expires表示服务器端告诉客户端当前资源的失效时间,截止到哪个时间点,是一个绝对时间,即过了这个时间点请求的话,就说明缓存已经失效啦,但是由于服务器端时间和客户端时间可能存在偏差,这也就是导致了最后缓存的时间误差,另一方面,该字段是http1.0提出来的,现在我们基本都是用cache-control:max-age:30来替代。

  1. Cache-Control
    • max-age: 3000:即 Cache-Control:max-age:5000;表示缓存在5s后会失效,是一个相对时间,但是要注意的是,该时间是从响应报文创建的时间就开始计时啦,但是由于可能网络等原因,例如网络传输过程花了3s, 那么实际客户端接收到数据以后,缓存的有效时间就剩2s啦。
    • no-store: 表示不允许缓存,通常一些频繁变化的页面,需要设置该选项。
    • no-cache: 注意不要别这个名字骗啦,该字段表示允许缓存,但是使用之前必须要先去服务器端验证是否过期,如果没过期,则使用缓存,如果过期了,则返回最新数据,(注意:注意如何验证有效,我们之后会讲到)
    • must-revalidate: 表示允许缓存,并且如果缓存不过期的话,先使用缓存,如果缓存过期的话,再去服务器端进行验证,(这里要注意:客户端只能根据max-age这个时间去判断缓存是否过期,超过该字段指定的时间了,说明缓存失效了,但是并不意味着这个资源有变化,所以还需要去服务器端验证,是否资源真的有变化,如果验证有变化,则返回最新资源,如果验证没变化,则返回304,然后更新max-age的失效时间,我们稍后会细讲)

以下是服务器端控制缓存的流程:

客户端的缓存控制

上面我们介绍了,服务器端如何在响应头中添加响应的字段来浏览来是否可以使用缓存,同样,客户端自己也可以控制,即浏览器也可以在请求中中添加Cache-Control等字段。

这里我们主要说三个场景:

  1. 浏览器刷新

即我们按F5刷新页面的时候,该页面的http请求中会添加:Cache-Control:max-age:0; 即说明缓存直接失效啦,就不走缓存了,直接从服务器端读取数据。

  1. 浏览器强制刷新

即我们按ctrl+f5强制刷新页面的时候,该页面的http氢气会添加:Cache-Control:no-cache; 即表示此时要首先去服务器端验证资源是否有更新,如果有更新则直接返回最新资源,如果没有更新,则返回304,然后浏览器端判断是304的话,则从缓存中读取数据。(注意:可能有的小伙伴会有疑问:我们去服务器端验证资源不就是重新发起了一次请求吗?是的,这句话没错,但是如果资源没有变化,在此次请求只会返回304的状态等相关头信息,请求的资源数据并不会返回,直接从缓存中读取就可以啦)

  1. 浏览器前进后退重定向

当我们点击浏览器的前进后退操作时,这个时候请求中不会有Cache-Control的字段,没有该字段,就表示会检查缓存,直接利用之前的资源,不再重新请求服务器。

如何去服务器端验证资源是否变化?

上面其实我们已经介绍了客户端是如何判断是否可以进行缓存,以及缓存的失效时间了,即通过Cache-Control:max-age或者no-store来告诉浏览器是否可以对数据进行缓存,以及缓存的失效时间。

但是呢,浏览器判断顶多是根据服务器端返回的失效时间去判断,这样并不一定准确,因为很可能出现缓存失效啦,但其实资源并没有发生变化,这个时候其实也是应该走缓存的,那如何判断资源有没有发生变化呢?这肯定只能交给服务器端来判断了,

那服务器端如何判断呢?即通过Last-Modified/if-Modified-Since,或者ETag/If-None-Match来判断即可,

  1. Last-Modified/if-Modified-Since

即该字段是服务器端返回给客户端的响应头字段,表示当前请求的资源的最后修改时间,如果响应头中有该字段,那么下次请求的时候,请求头中就会包含if-Modified-Since字段,它的值就是Last-Modified的值,这样服务器端收到该字段的值,就可以和对应的资源最终的修改时间做对比,如果发生变化,则说明资源发生了变化,则返回最新资源(此时状态码是200),如果没有发生变化,则返回304,浏览器从缓存中直接去数据即可。

  1. ETag/If-None-Match

使用资源的最后更改时间作为判断资源是否更改可能会有问题?比如:资源改了之后,又改了回来,这时虽然资源的最后修改时间发生了变化,但其实资源内容本身没有发生变化,其实这种情况也应该是走缓存的,所以才出现了ETag字段,表示资源的唯一标识,那如果响应头中有该字段,则下次请求的时候,请求头中就会有If-None-Match字段,它的值就是ETag的值,服务器端收到以后,就会和当前资源的唯一表识别去对比,如果不一样,则说明资源发生变化,返回最新数据即可(此时状态码是200),如果一样,则说明资源没有变化,返回304,浏览器从缓存中读取数据。

总结

  1. 首先,浏览器端会根据Cache-Control是否是no-store来判断是否可以对返回的数据进行缓存,如果是no-store表示不允许缓存,之后的请求都不会走缓存,而是重新想服务器端发送请求。
  2. 如果不是no-store,一般就是返回max-age: 5000;来告诉浏览器端可以对数据进行缓存,并且设置缓存的失效时间,通过max-age一般会搭配no-cache或者must-revalidate一起返回,no-cache和must-revalidate就是控制要去服务器端进行验证数据是否真的有变化。
  3. 那如何验证变化呢?就是借助Last-Modified/if-Modified-Since,或者ETag/If-None-Match来判断,如果确实有变化,则返回最新数据,如果没有变化,则返回304,同时更新缓存的失效时间。

以上就是缓存的整个工作机制,其实我们没必要去记忆什么强制缓存,协商缓存等概念,重要的是我们要理解缓存的整个设计思想,每一步的策略到底是解决了什么问题。

注意:文章中的缓存规则可能与浏览器最新的缓存规则有所差异,没来得及更新,这里重点讲解一下理解浏览器的缓存思路,可以帮助我们更好的理解缓存。