聊一聊HTTP缓存

HTTP 缓存作为 Web 重要的一环,理解并合理使用它能够提高 Web 性能。

  • 什么是 HTTP 缓存

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

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

缓存分为两种,Private cacheShared cache

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

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

/http-caching/http-cache-type.png

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

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

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

/http-caching/chrome-memeory-cache-disk-cache.png
淘宝网首页, Chrome Dev Tools 下观察到的协商缓存(304)和强缓存 (from xxx cache)

/http-caching/HTTPStaleness.png
强缓存和协商缓存验证资源是否过期

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

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

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

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

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

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

1
Cache-Control: no-cache

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

1
Cache-Control: private Cache-Control: public

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

1
Cache-Control: max-age=31536000 Expires: Wed, 21 Oct 2015 07:28:00 GMT
1
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 参考

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

/http-caching/HTTPVary.png
在响应头使用*Vary*字段条件验证*Content-Encoding

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

1
Vary: User-Agent
  1. https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching
  2. https://stackoverflow.com/questions/44596937/chrome-memory-cache-vs-disk-cache?rq=1
  3. https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests
  4. Weak and Strong Validators

相关内容