HTTP Cache 浅析

4,997 阅读7分钟

本文转载至

深入理解浏览器的缓存机制

HTTP Cache 为什么让人很困惑

查看栗子:

基于http协议讲解缓存头Cache-Control在服务中的应用

一、Private browser caches 和 Shared proxy caches

缓冲这块并不怎么好理解,网上查阅的资料,都讲的比较模糊,就比如:Private browser cachesShared proxy caches

浏览器缓存的服务架构可能是这样的:浏览器(Cache)<=>服务器。
代理服务器缓存架构可能是这样的:浏览器 <=> CDN(Cache)<=>源服务器。

不同的 HTTP Cache 解决的问题和使用的场景是不一样的。个人理解浏览器缓存主要是为了避免不必要的请求和大量的网络传输,而代理服务器缓存主要是为了让服务离用户更近更有效率(当然也解决了请求和网络传输)。

对于 Web 开发者来说,可能经常遇到的还是浏览器缓存,当我们访问一个网站的时候,会进行缓冲,当我们下一次访问的时候,可能看到的就是缓冲中加载的页面。这样可以大大加快访问速度。

二、正确理解 Cache-Control 指令

这个指令是一个通用首部字段,就是说这个指令能够作为请求和响应指令,同时这个指令的参数也有多个,比如说其参数 max-age = 0 在请求和响应指令中分别代表什么?在理解的时候一定要分辨清楚。

三、进一步理解 Cache-Control 指令

它有三个含义:

(1)能否缓存(针对响应来说)

private:表示它只应该存在与浏览器缓存。
public:表示它可以缓存在浏览器或者 CDN 上。
no-cache:这个词很迷惑,不是代表不能使用缓存,而是代表在使用前必须到服务器上确认。
no-store:表示不允许被缓存。

(2)缓存多久(针对响应来说)

max-age= 秒,告知浏览器这个缓存的有效时间多少。

(3)revalidation(针对响应来说,就是条件检查)

must-revalidate:
表示浏览器必须检查服务器,确认本地缓存是否有效,这个参数和请求参数 max-age = 0 有些类似。

这个指令形象的告诉浏览器,你是不是可以缓存这个对象,这个对象缓存时间是多少,是否在每次使用缓存的时候先确认下。

一张图描述的很清晰:

  • 这个资源是否允许缓存?
  • 客户端每次使用缓存的时候需要去服务器校验吗?
  • 这个缓存是 Public 的还是 Private?
  • 缓存时间多少?
  • 资源标识符是什么(Etag)?

四、Expires和Cache-Control两者对比

其实这两者差别不大,区别就在于 Expires 是http1.0的产物,Cache-Control是http1.1的产物,两者同时存在的话,Cache-Control优先级高于Expires;在某些不支持HTTP1.1的环境下,Expires就会发挥用处。所以Expires其实是过时的产物,现阶段它的存在只是一种兼容性的写法。

五、协商缓冲

协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:

  • 协商缓存生效,返回304和Not Modified

  • 协商缓存生效 ,返回200和请求结果

协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。

1.Last-Modified和If-Modified-Since

浏览器在第一次访问资源时,服务器返回资源的同时,在response header中添加 Last-Modified的header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和header

Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT

浏览器下一次请求这个资源,浏览器检测到有 Last-Modified这个header,于是添加If-Modified-Since这个header,值就是Last-Modified中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200

但是 Last-Modified 存在一些弊端:

如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源

既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略?所以在 HTTP / 1.1 出现了 ETagIf-None-Match

2.ETag和If-None-Match

Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。

3.两者之间对比:

  • 首先在精确度上,Etag要优于Last-Modified。

Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified也有可能不一致。

  • 第二在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。
  • 第三在优先级上,服务器校验优先考虑Etag

六、缓存机制

通常浏览器缓存策略分为两种:强缓存和协商缓存。如果缓存过期了,我们就可以使用协商缓存来解决问题。协商缓存需要请求,如果缓存有效会返回 304。

七、实际场景应用缓存策略

频繁变动的资源

Cache-Control: no-cache

对于频繁变动的资源,首先需要使用Cache-Control: no-cache 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。

不常变化的资源

Cache-Control: max-age=31536000

通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的max-age=31536000 (一年),这样浏览器之后请求相同的 URL 会命中强制缓存。

而为了解决更新的问题,就需要在文件名(或者路径)中添加hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。 在线提供的类库 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用这个模式。

八、用户行为对浏览器缓存的影响

所谓用户行为对浏览器缓存的影响,指的就是用户在浏览器如何操作时,会触发怎样的缓存策略。主要有 3 种:

  • 打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。

  • 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。

  • 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache),服务器直接返回 200 和最新内容。