当Web请求发起时,会先查看本地是否有该资源已缓存的副本,如果有,就直接从本地读取,而不需要从原始服务器中获取该资源。这样一来可以非常有效地减少冗余的数据传输,也可以减轻原始服务器的请求压力。下图就是一个请求的神奇之旅:

request flow

左边虚线框都是在缓存层完成,大部分情况下是指浏览器内部的缓存机制,也可以指一些缓存代理服务,如CDN等(本文不讨论);右边虚线框都是在服务器端完成的。

下面就按照这个流程图,客户端使用Chrome浏览器打开Google(但愿你看到的不是404 not found)来演示不同场景下的请求。

缓存未命中

cache miss

这是最简单的场景,所有资源都是从服务器返回,就好像没有缓存这一层一样。通常是资源从来没有请求过,或者在请求头部指明不使用缓存。Chrome开发者工具有个很方便的功能,就是可以设置当打开开发者工具时禁用缓存:

chrome disable cache

它的原理很简单,就是在每个请求前加上不使用缓存的头部cache-control: no-cachepragma: no-cache

no cache header

在给静态文件打版本号还没流行的时候,这个办法也常用来更新静态文件的缓存,即用Ajax设置cache-control: no-cachepragma: no-cache的头部将所有静态文件请求一遍。

缓存命中

cache hit

这是我们最期望的结果。从上图看这个时候没有服务器什么事,如果是全静态内容,而且已被全缓存,即使没有网络连接也可以正常访问。

是否缓存

浏览器对请求过的资源默认都会按请求url缓存起来,这样再次请求时就可以直接返回缓存的对象。

chrome view http cache

即使设置了cache-control: no-cachepragma: no-cache头部也依然会缓存,除非响应头部设置了cache-control: no-store就不会将该资源缓存。

是否过期

资源不是创建好就永远不会再变更,服务器端为防止缓存的资源与最新版不一致,就会给这些资源设置一个过期时间的响应头部,缓存就会按照这个过期时间存放资源,一旦过期,就会跟服务器校验,检查这个资源是否变更,如果有变更,就获取最新版。

过期时间可以由HTTP/1.0+的Expires头部和HTTP/1.1的Cache-Control: max-age头部来指定。前者是设置一个GMT的绝对过期时间,如:Expires: Wed, 20 Apr 2016, 09:11:33 GMT;后者是设置一个相对于资源第一次创建的存活时间(单位为秒),如:Cache-Control: max-age=3600,就表示在一小时后过期。

由上可知,缓存命中需要满足两个条件:

  1. 已缓存
  2. 缓存没有过期

命中缓存,Chrome开发工具会标记出from cache,而且request请求体也没那么复杂了😄 。

缓存再验证命中

cache revalidation hit

上一场景中提到缓存资源都有一个过期时间,在当前时间超过这个过期时间后,客户端就不确定这个资源是否可信了,于是要询问服务器,这时会发一个If-Modified-Since头部,并带上缓存好的资源最后修改时间(这个时间是服务器的响应头部Last-Modified),如:If-Modified-Since: Wed, 13 Apr 2016, 09:11:33 GMT。服务器收到请求后,当发现有If-Modified-Since头部时,会将该时间与服务器端该资源的最后修改对比,如果相等,就会返回一个小的响应体,即:304 Not Modified;而不用返回一个比较大的完整资源。缓存收到304 Not Modified后,就知道这个资源没有变更,还可以继续使用,于是修改一下过期时间,然后当什么都没发生过。

这个场景,也叫作“缓慢命中”,虽然有与服务器通信,但是一旦命中,依然能节省不少的带宽资源。

用最后修改时间作为评判资源变更的唯一标准,这显然是不严谨的,有的资源在服务器端定时生成,修改时间一直在变,但内容不一定变化。这时可以用ETagIf-None-Match替代上面的Last-ModifiedIf-Modified-SinceETagLast-Modified也可以同时设置,同时验证。ETag一般用来设置文件的版本号,可以使用文件md5值,如ETag: "c9f8b79751fe3b975999de6cd82759ba"

缓存再验证未命中

cache revalidation miss

这个场景与上一个类似,只是在验证变更这一步出现不相等的结果。这时服务器会返回一个完整的200响应体,并带上最新的Last-ModifiedETag(如果有设置),缓存会将副本更新然后传递给客户端。

总结

本文主要讲解基于http协议的Web缓存是怎么回事,内容大量参考自《HTTP权威指南--第7章 缓存》。缓存是把双刃剑,用得好可以非常显著提升性能,用得不好请刷新浏览器😓 。