私有缓存
私有缓存是绑定到特定客户端的缓存 ---通常是浏览器缓存。由于存储的响应不与其他客户端共享,因此私有缓存可以存储该用户的个性化响应
Cache-Control: private
- 个性化内容通常是由 cookie 控制,但是 cookie 的存在并不能表明他是私有的,因此单独的 cookie 不会使响应成为私有的。
- 需要注意的是,如果响应具有 Authorization 标头,则不能将其存储在私有缓存(或共享缓存,除非 Cache-Control 指定的是 public)。
共享缓存
共享缓存位于客户端和服务端之间,可以存储能在用户之间共享的响应。共享缓存可以细分为代理缓存和托管缓存
代理缓存
除了访问控制的功能外,一些代理还实现了缓存以减少网络流量。这通常不由服务开发人员管理,必须恰当的HTTP标头控制
Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate
托管缓存
托管缓存由服务人员明确部署,以降低源服务器负载并有效地交付内容。示例包括:反向代理、CDN、service worker 与缓存 API 的组合。- 大多数情况下,你可以通过 Cache-Control 标头和你自己的配置文件或仪表板来控制缓存的行为
- HTTP 缓存规范本质上没有定义显式删除缓存的方法 --- 但是使用托管缓存,可以通过仪表板操作、API 调用、重新启动等实时删除已经存储的响应。这允许更主动的缓存策略。
- 也可以忽略HTTP 缓存闺房协议以支持显示操作。例如指定: Cache-Control: no-store 来退出私有缓存或代理缓存,同时使用你自己的策略尽在托管缓存中进行缓存。
- 某些 CDN 提供自己的标头,这些仅对该CDN有效(如:Surrogate-Control )。目前正在努力定义一个 CDN-Cache-Control 标头来标准化这些标头
启发式缓存
HTTP 旨在尽可能多地缓存,因此即使没有给出 Cache-Control,如果满足某些条件,响应也会被存储和重用。这称为启发式缓存。
启发式缓存是在 Cache-Control 被广泛采用之前出现的一种解决方法(未指定过期时间,客户端尝试性地重用数据,时间约为修改前至今的 10%的时间),基本上所有响应都应明确指定 Cache-Control 标头。
基于 age 的缓存策略
存储的 HTTP 响应有两种状态: fresh 和 stale 。fresh状态通常表示响应仍然有效,可以重用。而 stale 状态表示缓存的响应已经过期。
确定响应何时是 fresh 的和何时是 stale 的标准是 age。在 HTTP 中,age 是自响应生成以来经过的时间。
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1024
Date: Tue, 22 Feb 2022 22:22:22 GMT
Cache-Control: max-age=604800
<!doctype html>
...
存储示例响应的缓存会计算响应生成后经过的时间,并将结果用作响应的 age。
对于该示例的响应,max-age 的含义如下:
如果响应的 age 小于一周,则响应为 fresh。
如果响应的 age 超过一周,则响应为 stale。
只要存储的响应保持有效(fresh),它将用于兑现客户端请求。
Expires 或 max-age
- 在HTTP/1.0 中,有效期是通过 Expires 标头来指定的。
Expires 标头使用明确的时间来缓存生命周期
Expires: Tue, 28 Feb 2022 22:22:22 GMT
但是时间格式难以解析,也有可能通过故意偏移系统时钟来诱发问题,一般不使用
- 在HTTP/1.1 中,Cache-Control 采用了 max-age ---用于指定经过的时间
若Expires 和 Cache-Control: max-age 都可用,则将 max-age 定义为首选
验证响应
验证即为协商缓存:使用包含 If-Modified-Since 或 If-None-Match 请求标头的条件请求完成的。
强制验证:
-
Cache-Control: no-cache
-
Cache-Control: max-age=0, must-revalidate
max-age=0意味着立即过时,must-revalidate意味着一旦过时不得在没有重新验证的情况下重用它。
no-cache 指令不会阻止响应的存储,而是阻止在没有重新验证的情况下重用响应。
不使用缓存
Cache-Control: no-store
No-store 指令会禁止响应存储在任何缓存中
不与其他用户共享
Cache-Control: private;
重新加载、强制重新加载 与 避免重新验证
- 重新加载
GET / HTTP/1.1
Host: example.com
Cache-Control: max-age=0
If-None-Match: "deadbeef"
If-Modified-Since: Tue, 22 Feb 2022 20:20:20 GMT
// 注意:"reload"不是正常重新加载的正确模式;"no-cache"才是
fetch("/", { cache: "no-cache" });
- 强制重新加载
GET / HTTP/1.1
Host: example.com
Pragma: no-cache
Cache-Control: no-cache
Fetch标准
// 注意:"reload"------而不是"no-cache"------是"强制重新加载"的正确模式
fetch("/", { cache: "reload" });
-
避免重新验证
-
永远不会改变的内容应该被赋予一个较长的 max-age,方法是使用缓存破坏------也就是说,在请求 URL 中包含版本号、哈希值等。
-
immutable 指令可用于明确指示不需要重新验证,因为内容永远不会改变。
Cache-Control: max-age=31536000, immutable
删除存储的响应
-
基本没有办法删除用很长的 max-age 存储的响应。
-
由于缓存,不再有请求到达服务器!
-
规范中提到的方法之一是使用不安全的方法(例如:POST)发送对同一URL对请求,但对于许多客户端而言,通常很难故意这样做
-
有一个 Clear-Site-Data: cache 标头和值的规范,但并非全部浏览器都支持。---即使使用它,他也只会影响浏览器缓存,而不会影响中间缓存。
-
除非用户手动执行重新加载、强制重新加载或清除历史操作,否则存储的响应都将保留其 max-age 期间。
-
缓存减少了对服务器的访问,也意味着服务器失去了对该URL的控制。可以通过添加 no-cache 以便服务器始终接受请求并发送预期的响应
请求折叠
共享缓存主要位于源服务器之前,旨在减少到源服务器的流量。
因此,如果多个相同的请求同时到达共享缓存,中间缓存将代表自己将单个请求转发到源,然后源就可以将结果重用于所有客户端。这称为请求折叠
请求同时到达时会发生请求折叠,因此即使响应中给出了 max-age=0 或 no-cache,它也会被重用
如果响应是针对特定用户个性化的,并且你不希望它在折叠中共享,则应添加 private 指令。
[图片]
常见的缓存模式
默认设置
缓存的默认行为(即对于没有 Cache-Control 的响应) 不是简单的"不缓存",而是根据所谓的"启发式缓存"进行隐式缓存。
为了避免"启发式缓存",最好显式地为所有响应提供一个默认的 Cache-Control 标头,一般为:
Cache-Control: no-cache
另外,如果服务实现了 cookie 或其他登录方式,并且内容是为每个用户个性化的,那么也必须提供 private ,以防止与其他用户共享:
Cache-Control: no-cache, private
缓存破坏
-
可以使用包含基于版本号或哈希值的更改部分的 URL 来提供 JS 和 CSS。
-
由于缓存根据他们的 URL 来区分资源,因此如果在更新资源时 URL 发生变化,缓存将不会在此被重用
-
缓存破坏是一种在内容更改时修改 URL 来使响应在很长一段时间内可缓存的技术。该技术可以应用于所有子资源,例如图像
主要资源
-
与子资源不同,主资源不能使用缓存破坏,因为他们的URL不能像子资源URL一样被修饰。
-
对于这种情况, no-cache 将是合适的
托管缓存的更多信息
-
缓存主要资源很困难,因为仅使用 HTTP 缓存规范中的标准指令,在服务端上更新内容时无法主动删除缓存内容。
-
但是,可以通过部署托管缓存( 比如 CDN 或 service worker )来实现。
-
Service Worker