Skip to content

HTTP 的缓存

通过网络获取服务器中的内容是缓慢和成本很高的,那么对于那些我们不需要更新的资源进行缓存下来,在下次请求的时候重复使用那么就成为性能优化的一个重要方面

知识点

1. 分类

  • 强缓存

  • 对比缓存

2. 状态码

  • 304

  • 200(from cache)

  • 200(from disk)

3. 请求头字段

  • expires

  • cache-control

  • last-modified

  • if-modify-since

  • ETag

  • if-None-Match

缓存规则

我们将一张图片缓存下来,难道我们就不会修改这张图片了么?肯定不可能的,那么我们更新了这个图片的内容,这时候如何触发浏览器去获取新的图片内容?

  1. 我们直接修改图片的地址。

  2. 定义时效性

这时候浏览器就推出了 2 种缓存规则: 强缓存、对比缓存

强缓存

强制缓存下来,在资源未失效之前一直使用,这时候你在服务器修改内容,是完全没有作用的。。

强缓存

对比缓存

强缓存只关注资源是否失效,这是有问题的,如果我们服务器资源文件修改了,那就不能及时的通知客户端去获取新的资源文件了。

但是网络通讯的成本是很大的,一个资源文件都很大,那么这时候我们能不能这样,我们不去校验资源文件的内容是否修改了,但是我们可以给每一个资源文件一个编号, 这时候我们每次命中缓存文件的时候,我们只需要发送这个编号去校验一下资源文件是否有效,这样一个编号的大小是很小的。这时候对比缓存就出现了。

对比缓存是: 在缓存文件命中的情况下通过一个编号去与服务器校验文件的有效性。

对比缓存

两种缓存对比

我们发现两种缓存规则的差异,强缓存如果命中不需要与服务器通信,对比缓存如果命中,仍然需要与服务器进行通信

但是如果两种缓存规则都存在,那么强缓存优先级高于对比缓存,即当执行强制缓存的规则时,如果缓存生效,直接使用缓存,不再执行对比缓存规则。

强缓存

在 HTTP1.0 的时候对于缓存的控制使用的 Expires 字段,这个字段的值是一个绝对时间的 GMT 格式的时间字符串,代表资源的失效时间, 但是这个时间是服务器的时间,如果客户端的时间与服务器不同那么就会导致缓存失效

在 HTTP1.1 的时候使用 Cache-Control

  • private 只能被终端用户的浏览器缓存,不允许 CDN 等中继缓存服务器对其缓存。

  • public 可以被所有的用户缓存,包括终端用户和 CDN 等中间代理服务器。

  • max-age 该值是一个相对时间,代表该资源在客户端的有效期

  • no-cache 不是不缓存的意思,而是需要进行对比缓存,发送请求到服务器验证有效性 (对比缓存)

  • no-store 这个才是真正的不缓存

对比缓存

HTTP1.0

在 http1.0 的时候,对比缓存使用的 Last-Modified 和 if-modify-since

在第一次请求资源的时候服务器会在 response 的 Last-Modified 保存当前资源最后修改时间, 然后在客户端再次请求的时候,会在 request 的 header 中将 if-Modify-Since 设置成资源上次返回的 Last-Modified 的值。 这时候服务器收到 request.if-Modify-Since 然后跟资源的最后修改时间进行比较,如果相同那就返回 304 状态码,不同就返回 200,并返回新的 Last-Modified

在 HTTP1.1 的时候,对比缓存使用的是 ETag 和 if-None-Match 进行对比校验

在第一次请求资源的时候服务器会在 response 的 Etag 保存当前资源的一个唯一标示(字符串、hash、文件 size 等等), 然后在客户端再次请求的时候,会在 request 的 header 中将 if-None-Match 设置成资源上次返回的 ETag 的值。 这时候服务器收到 request.if-None-Match 然后跟资源的 Etag 进行比较,如果相同那就返回 304 状态码,不同就返回 200,并返回新的 ETag

问题:

  1. 为什么不使用原来的 Last-Modified 和 if-modify-since,而是采用新的 ETag 和 if-None-Match,且其核心过程没有改变,只是修改了对比的值的类型?

因为 Last-Modified 是毫秒级的如果在服务器端毫秒级内重复修改了资源文件,那么就无效了。 现在讲究分布式服务,那么如果一个客户端在访问同一个域名的时候匹配到不同的服务器,那么资源的最后修改时间是不会相同的,这时候 304 就变成了 200 了。

  1. 如何配置 ETag(Nginx)

这个最好不要在 Nginx 那边去设置 ETag,为什么?

  • 无论你对源代码做了什么改动,比如说改动了注释,改动了空行什么的,ETag 都会变

  • 对比缓存(304),仍然需要发送一次请求

  • 负载均衡的时候,不同的物理机相同的文件的 inode 不同,会生成不同的 ETag(导致客户端 200(from cache)和 304 not modify 交替出现)

  1. Etag 的问题

ETag 也有他自己的问题,所以经常在使用中会关闭 ETag。举例来说,同一个文件在不同物理机上的 inode 是不同的,这就导致了在分布式的 Web 系统中,访问同一域名下部署在不同服务器上的同一文件,由于 ETag 不同,会多触发一次 HTTP 请求(304 变成 200)。解决办法也有从 ETag 算法中剥离 inode,只是使用 mtime,但是这样实际上和 Last-Modified 就一样了。当然你也可以额外的做一些改进,使 ETag 对静态资源的算法也是通过 hash 计算的。不过由于一般我们会使用 CDN 技术,因此集群部署中的 ETag 问题并不会造成太大的影响,所以折腾的人也不太是很多。

  1. 304 , 200(from cache) , 200(from disk)
  • 304 代表当前文件使用了对比缓存,向服务器发送了一个请求校验文件的有效性,且服务器返回 304 说明文件是可以使用的。

  • 200(from cache) , 200(from disk)

当使用强缓存的时候,我们请求文件如果命中那么文件直接从缓存服务器获取到文件数据,这时候缓存服务器分为 cache(内存)和 disk(磁盘), 如我们请求一个文件,其 cache-control:max-age: 300,那么在 300 秒内再次请求的时候这时候文件是存放在内存中的 所以返回 200(from cache), 如果我们这时候关闭了页签,那么浏览器就会清理内存中的数据,这时候因为文件还在有效期所以会存放到 disk 磁盘中,如果还在 300 秒内再次请求此网站, 那么这时候强缓存命中,就会返回 200(from disk)

  1. 这里面涉及到 强缓存、对比缓存、HTTP1.0、HTTP1.1,那么如果同时触发权重如何比较
  • 现在浏览器均默认使用 HTTP 1.1,所以基本上都是 1.1 的规范大于 1.0 的规范,即 cache-control > expires , (ETag 和 if-None-Match) > (Last-Modified 和 if-modify-since)

  • 强缓存优先于对比缓存,所以如果资源设置的 Cache-control:max-age=300 在 300 秒内不管怎么请求都会触发强缓存命中,etag 是无效的。 如果失效了或者 Cache-control:max-age=0 等等,这时候会启用对比缓存。

  1. F5,浏览器输入回车, Ctrl+F5 等刷新对缓存的影响

对于一个文件,其设置了 Cache-control:max-age=300,那么在 300 秒内各操作的反应是

  • 浏览器输入回车: 强缓存命中,直接使用缓存数据

  • F5 : 不管你强缓存,会直接使用对比缓存跟服务器进行确认有效性

  • Ctrl+F5 : 最绝了,直接删除所有的缓存数据,全部重新来