HTTP 缓存
缓存的作用
重用已获取的资源能够有效的提升网站与应用的性能。Web 缓存能够减少延迟与网络阻塞,进而减少显示某个资源所用的时间。借助 HTTP 缓存,Web 站点变得更具有响应性。
什么是缓存
缓存是一种保存资源副本并在下次请求时直接使用该副本的技术。当 Web 缓存发现请求的资源已经被存储,它会拦截请求,并返回该资源的副本,而不会去服务器重新下载。使用缓存有下列的优点:
- 减少冗余的数据传输
- 缓解服务器端的压力,提升性能
- 降低了距离时延,比如 CDN,能加快访问速度
缓存类型
缓存类型有很多,常用的缓存类型有两种:私有缓存、共享缓存。共享缓存能被多个用户使用,而私有缓存只能被单个用户使用。
缓存中的一些概念
在介绍缓存的策略之前,我们需要先了解下关于缓存的一些概念。
缓存命中(cache hit)
指的是可以用已有的副本为某些到达缓存的请求提供服务
缓存未命中(cache miss)
Web请求没有缓存可以使用,而转发给原始服务器
HTTP 再验证 (revalidation)
也称新鲜度检测。原始服务器的内容可能会发生变化,缓存要不时对其进行检测,看看它们保存的副本是否仍是服务器上最新的副本
再验证命中(revalidate hit)
缓存 GET 请求的流程图
流程图来自于 《HTTP 权威指南》
缓存控制
都有哪些手段可以控制缓存?
- 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 权威指南》