聊一聊HTTP缓存

484 阅读5分钟

前言

  • 什么是HTTP缓存?

  • 为什么需要HTTP缓存,因为使用HTTP缓存可以减少请求响应时间和避免网络时延带来的等待时间。

  • 如何合理使用缓存,设置不同文件的缓存时间?

缓存的种类

缓存分为两种,Private cacheShared cache

  • Private cache是针对单个用户而言,一个例子就是自己的浏览器缓存,使用这些缓存实现了浏览器的前进/后退,而无需再次请求资源

  • Shared Cache针对多个用户,比如代理服务器,一个ISP (Internet Service Provider)或者公司都有代理服务器,用来缓存一些资源,供所有员工访问,而不必再向互联网发出请求,避免网络延迟

缓存控制

要清楚缓存控制,这里要先分清两个概念,一个叫做缓存存储(Cache Storage),一个叫做缓存(Caching)

其实在中文里面,可能直接都说成缓存,但是这样不助于理解下面缓存控制的前两种情况。其实缓存存储是一个实实在在的东西,而缓存只是它的一个动作

后来我又发现国人有强缓存协商缓存的说法,强缓存就是指Cache Storage会直接返回Cache的内容,协商缓存是指Cache Storage向原始服务器进行验证,如果原始服务器返回304 Not ModifiedCache Storage才返回Cache的内容

淘宝网首页, Chrome Dev Tools下观察到的协商缓存(304)和强缓存 (from xxx cache)

强缓存和协商缓存验证资源是否过期

上面一幅图中,304 Not Modified表示使用了协商缓存,而from memory cachefrom disk cache则直接使用强缓存

Chrome在某个版本之后将以前的from cache变成了from disk cachefrom memory cache,就跟名字一样,缓存在磁盘中的内容即使在关闭了当前tab之后还能使用,但是缓存到内存的内容,当关闭tab或者浏览器,或者浏览器崩溃后,就消失了。但是memory cache加载速度更快,从上图也能看出

关于强缓存和协商缓存,我没发现这两个词的英文出处,但是这两个词理解起来更形象,所以下面的缓存控制情况我也会用到这两个词

1. 不使用缓存存储

可以理解为每次的请求都不经过Cache Storage,直接发给服务器,返回新的对象

Cache-Control: no-store
Cache-Control: no-cache, no-store, must-revalidate

2. 不直接使用缓存内容

Cache Storage会缓存内容,但是每次需要向服务器验证资源是否过期,没过期才能使用缓存,这就是304 Not Modified的情况,也就是我们所说的协商缓存

Cache-Control: no-cache

3. 公共缓存和私有缓存

例如告诉资源只能被Private Browser Cache缓存起来还是同时也能被Public Proxy Server缓存起来

Cache-Control: private
Cache-Control: public

4. 强缓存过期控制

Cache-Control优先级高于Expired,前者是相对时间,后者是绝对时间;前者为General Header的字段,后者为Response Header的字段

Cache-Control: max-age=31536000 
Expires: Wed, 21 Oct 2015 07:28:00 GMT

5. 资源验证

Cache-Control: must-revalidate

缓存验证

缓存验证发生在用户刷新,或者响应头中包含Cache-Control: must-revalidate的情况

缓存验证有两种形式,都可以用来验证文档是否过期

  • 文档的最后修改日期,响应头Last-Modified字段,请求头If-Modified-Since字段请求验证
  • 对文档当前版本的唯一标识,叫做entity tag,响应头ETag字段,通常使用MD5 Hash实现,请求头If-None-Match字段请求验证

同时,缓存验证有两种验证类型

  • 强验证(strong validator),文档对比一个字节一个字节地进行,严格对比
  • 弱验证(weak validator),文档只有语义不同才认为是改变,例如一个HTML文档也许只是里面的广告发生改变,或者是页脚的日期发生改变,弱验证会认为它们还是相同的

HTTP默认使用的是强验证

需要注意的是,一般情况下,Last-Modified字段只能作为弱验证,因为它的最小单位是秒,一秒内文档可能改变2次,无法做到标识每一次改变,如果使用它做强验证,则需要明确知道不会发生这种情况。当然,还有一些其他的限制,具体的限制可以看我下面的第4条RFC文档参考,这里就不细说了

ETag默认作为强验证,如果需要用它实现弱验证比较困难,因为需要判断文档中不同元素的语义,确保语义变化时ETag的值才发生变化,而不是每次检测到字节变化时就生成一个新的值

缓存验证这部分建议有能力的同学直接查看第4条RFC参考

条件缓存(响应头增加Vary字段)

该字段指出了筛选条件,如果下一次到达Cache Storage的请求中的该条件与缓存时的该条件的值不一样,则重新向真实server发请求,否则使用缓存,看下面这幅图

在响应头使用Vary字段条件验证Content-Encoding

Vary字段通常会对User-Agent进行验证,避免将移动端缓存的文档直接发给桌面端请求的用户,反之亦然

Vary: User-Agent

参考

  1. developer.mozilla.org/en-US/docs/…
  2. stackoverflow.com/questions/4…
  3. developer.mozilla.org/en-US/docs/…
  4. Weak and Strong Validators