缓存分类
- 共享缓存:存储的响应能够被多个用户使用。
- 私有缓存:只能用于单独用户。
缓存方式
- 浏览器与代理缓存
- 网关缓存
- CDN
- 反向代理缓存和负载均衡器
操作的目标
常见的 HTTP 缓存只能存储 GET 响应,对于其他类型的响应则无能为力。缓存的关键主要包括 request method 和目标 URI(一般只有 GET 请求才会被缓存)。
缓存控制
没有缓存
缓存中不得存储任何关于客户端请求和服务端响应的内容。每次由客户端发起的请求都会下载完整的响应内容。
1 | Cache-Control: no-store |
缓存但重新验证
每次有请求发出时,缓存会将此请求发到服务器,服务器端会验证请求中所描述的缓存是否过期,若未过期,则缓存才使用本地缓存副本。
1 | Cache-Control: no-cache |
私有和公共缓存
- “public” 指令表示该响应可以被任何中间人(译者注:比如中间代理、CDN 等)缓存。
- “private” 则表示该响应是专用于某单个用户的,中间人不能缓存此响应,该响应只能应用于浏览器私有缓存中。
1 | Cache-Control: private |
过期
过期机制中,最重要的指令是 "max-age=<seconds>"
,表示资源能够被缓存(保持新鲜)的最大时间。相对 Expires
而言,max-age
是距离请求发起的时间的秒数。针对应用中那些不会改变的文件,通常可以手动设置一定的时长以保证缓存有效,例如图片、css
、js
等静态资源。
1 | Cache-Control: max-age=31536000 |
s-maxage
仅适用于共享缓存(CDN
),优先级高于 max-age
和 Expires
1 | Cache-Control: s-maxage=3600 |
验证方式
当使用了 “must-revalidate” 指令,那就意味着缓存在考虑使用一个陈旧的资源时,必须先验证它的状态,已过期的缓存将不被使用。
1 | Cache-Control: must-revalidate |
Pragma 头
Pragma 是 HTTP/1.0 标准中定义的一个 header 属性,请求中包含 Pragma 的效果跟在头信息中定义 Cache-Control: no-cache 相同,但是 HTTP 的响应头没有明确定义这个属性,所以它不能拿来完全替代 HTTP/1.1 中定义的 Cache-control 头。通常定义 Pragma 以向后兼容基于 HTTP/1.0 的客户端。
新鲜度
理论上来讲,当一个资源被缓存存储后,该资源应该可以被永久存储在缓存中。
缓存驱逐
由于缓存只有有限的空间用于存储资源副本,所以缓存会定期地将一些副本删除。当服务器上面的资源进行了更新,那么缓存中的对应资源也应该被更新,由于 HTTP 是 C/S 模式的协议,服务器更新一个资源时,不可能直接通知客户端更新缓存,所以双方必须为该资源约定一个过期时间,在该过期时间之前,该资源(缓存副本)就是新鲜的,当过了过期时间后,该资源(缓存副本)则变为陈旧的。驱逐算法用于将陈旧的资源(缓存副本)替换为新鲜的,注意,一个陈旧的资源(缓存副本)是不会直接被清除或忽略的,当客户端发起一个请求时,缓存检索到已有一个对应的陈旧资源(缓存副本),则缓存会先将此请求附加一个 If-None-Match 头,然后发给目标服务器,以此来检查该资源副本是否是依然还是算新鲜的,若服务器返回了 304 (Not Modified)(该响应不会有带有实体信息),则表示此资源副本是新鲜的,这样一来,可以节省一些带宽。若服务器通过 If-None-Match 或 If-Modified-Since 判断后发现已过期,那么会带有该资源的实体内容返回。
缓存的先后顺序
对于含有特定头信息的请求,会去计算缓存寿命。
- max-age:Cache-control: max-age=N 的头,相应的缓存的寿命就是 N。
- expires:通过比较 Expires 的值和头里面 Date 属性的值来判断是否缓存还有效。
- Last-Modified: 缓存的寿命就等于头里面 Date 的值减去 Last-Modified 的值除以 10(注:根据 rfc2626 其实也就是乘以 10%)
1 | expirationTime = responseTime + freshnessLifetime - currentAge |
注:responseTime 表示浏览器接收到此响应的那个时间点。
缓存验证
用户点击刷新按钮时会开始缓存验证。
- 如果缓存的响应头信息里含有”Cache-control: must-revalidate”的定义,在浏览的过程中也会触发缓存验证。
- 在浏览器偏好设置里设置 Advanced->Cache 为强制验证缓存也能达到相同的效果。
当缓存的文档过期后,需要进行缓存验证或者重新获取资源。只有在服务器返回强校验器或者弱校验器时才会进行验证。
ETags
- 作为缓存的一种强校验器,ETag 响应头是一个对用户代理(User Agent, 下面简称 UA)不透明(译者注:UA 无需理解,只需要按规定使用即可)的值。对于像浏览器这样的 HTTP UA,不知道 ETag 代表什么,不能预测它的值是多少。如果资源请求的响应头里含有 ETag, 客户端可以在后续的请求的头中带上 If-None-Match 头来验证缓存。
- Last-Modified 响应头可以作为一种弱校验器。说它弱是因为它只能精确到一秒。如果响应头里含有这个信息,客户端可以在后续的请求中带上 If-Modified-Since 来验证缓存。
当向服务端发起缓存校验的请求时,服务端会返回 200 ok 表示返回正常的结果或者 304 Not Modified(不返回 body)表示浏览器可以使用本地缓存文件。304 的响应头也可以同时更新缓存文档的过期时间。
浏览器刷新
- 地址栏输入地址回车:走正常的缓存流程
- 刷新页面:让强缓存过期,走协商缓存逻辑
- 强制刷新页面: 让强缓存和协商缓存都过期,不走缓存逻辑
CDN
内容分发网络,分为推和拉模式。
- 推模式:有更新时会强制推送到网络的各个节点
- 拉模式:有请求时会判断当前节点是否有数据且数据是否过期,然后再向源站请求资源
时间计算
拉模式下,所有节点的缓存都设置 5 分钟,浏览器可能拉到前 10 分钟到当前时间的资源(假设当前节点到源节点之间没有中间节点),原因如下
- 浏览器到当前节点数据缓存 5 分钟
- 当前节点到源数据可能缓存 5 分钟