缓存的作用

重用已获取的资源能够有效的提升网站与应用的性能。Web 缓存能够减少延迟与网络阻塞,进而减少显示某个资源所用的时间。借助 HTTP 缓存,Web 站点变得更具有响应性。

什么是缓存

缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。当 Web 缓存发现请求的资源已经被存储,它会拦截请求,并返回该资源的副本,而不会去服务器重新下载。使用缓存有下列的优点:

  • 减少冗余的数据传输
  • 缓解服务器端的压力,提升性能
  • 降低了距离时延,比如 CDN,能加快访问速度

缓存类型

缓存类型有很多,常用的缓存类型有两种:私有缓存共享缓存。共享缓存能被多个用户使用,而私有缓存只能被单个用户使用。

缓存中的一些概念

在介绍缓存的策略之前,我们需要先了解下关于缓存的一些概念。

缓存命中(cache hit)
指的是可以用已有的副本为某些到达缓存的请求提供服务
缓存未命中(cache miss)
Web请求没有缓存可以使用,而转发给原始服务器
HTTP 再验证 (revalidation)
也称新鲜度检测。原始服务器的内容可能会发生变化,缓存要不时对其进行检测,看看它们保存的副本是否仍是服务器上最新的副本

再验证命中(revalidate hit)

缓存 GET 请求的流程图

流程图来自于 《HTTP 权威指南》
HTTP 缓存 GET 流程图

缓存控制

都有哪些手段可以控制缓存?

  • Cache-Control
  • Expires
  • If-Modified-Since
  • Etag

这上面四个的缓存控制能力排行是 Cache-Control > Exppires > Etag > If-Modified-Since.

Cache-Control

Cache-Control: max-age 。 max-age 可以设置了文档的最大使用期(以秒为单位)。需要注意的是,max-age 使用的是相对时间(相对于文档第一次文档生成的时间)。
其中 Cache-Control 请求指令有:

  • Cache-Control: max-age= 最大使用期
  • Cache-Control: max-stale[=] 指定时间内,文档不能过期
  • Cache-Control: min-fresh= 在未来至少 seconds 秒要文档保持新鲜
  • Cache-Control: no-cache 不读取本地缓存,要求到服务器进行再验证
  • Cache-Control: no-store 请求和响应不存储任何缓存
  • Cache-Control: no-transform 告知代理,不要改变媒体类型
  • Cache-Control: only-if-cached 只有当缓存中有副本存在时,客户端才会获取一份副本

Expires

Cache-Control 作用相似,用于设置文档的最大使用期。也可以设置 max-age=Date值。二者的区别是, expires 使用的是绝对时间
而绝对时间依赖于计算机时钟的正确设置。如果计算机时钟设置错误或者跨时区的话,则会导致同样的设置在不同的时钟设置的计算机上表现不同。

基于这一点,所以更倾向于 Cache-Control。因为 Cache-Control 的控制力比 Expires 更高。

If-Modified-Since: / Last-Modifed

这是一组请求首部和响应首部。

当客户端发起再验证请求时,HTTP 首部会带上 If-Modified-SInce: Date。

  • 如果自指定日期后,文档被修改,则需要返回带有新的首部:Last-Modified: Date新的文档。下次客户端再发起请求,会将 Last-Modified 的值加入到下一次请求的 If-Modified-Since 中。
  • 如果自指定日期后,文档没有被修改,则返回 304 Not Modified.

If-None-Match / Etag

这也是一组请求首部和响应首部。

原理类似,服务器端返回资源时,如果头部带上了 Etag,那么资源下次请求时就会把值加入到请求头 If-None-Match 中,服务器可以对比这个值,确定资源是否发生变化,如果没有发生变化,则返回 304。

其中的 Etag 称为 “实体标签”,可以是包含文档的序列号或版本名,或者是文档内容的校验和及其他指纹信息等。

之所以需要使用 Etag 进行缓存控制是因为在有些情况下仅使用最后修改时间是不够的。

  • 有些文档可能会被周期性写入,但内容可能并不会变化,但是修改日期会发生变化
  • 有些文档可能被修改了,但是修改的内容并不重要,不足以让所有的缓存都失效(比如对拼写或者注释的修改)
  • 有些服务器无法准确地判定其页面的最后修改日期
  • 有些服务器提供的文档会在亚秒间隙发生变化(比如,实时监视器),对这些服务器来说,以一秒为粒度的修改时间显然不够用

在上述的这些情况,都需要使用 Etag 来管理缓存,会更加高效。

那么问题来了,何时该使用 Etag 和 If-Modified-Since?
答案是:返回谁用谁,都返回都用。

如果服务器只返回了 Etag,那么客户端请求的头部就必须要加上 If-None-Match。如果服务器返回了 Last-Modified, 那则需要使用 If-Modified-Since。如果都返回,则两种都用。

参考资料

  • 《HTTP 权威指南》