SYSTEMS NOTES阅读地图
8个主要章节
4张图片与图示
4组对照与手算表

从一个具体请求或状态变化出发,追踪数据流与方法成立的条件。

章节速览8 节 · 展开选择

网络基础解释一次请求怎样到达服务器,LRU解释空间不足时淘汰哪个条目。HTTP 缓存还要回答另一个问题:条目虽然仍在,下一次请求能否直接使用它?

答案依赖表示的身份、缓存指令、年龄和验证结果。先把这几个条件分开,就不会把“命中缓存”“没有下载正文”和“没有发送请求”混成同一件事。

先固定这篇文章的范围

本文以私有缓存、GET、完整 200 响应、显式 max-age 为主线,服务器提供 ETag 时使用条件验证。图和时间数值均为自拟教学例子。内部状态模型不实现共享代理的全部规则、启发式新鲜度、范围响应、缓存请求指令或允许过期复用的扩展,不把一次模型核验当作真实浏览器兼容性测试。

协议依据为 RFC 9111:HTTP Caching 和 RFC 9110:HTTP Semantics。私有缓存服务单个用户,共享缓存还涉及 private、s-maxage、认证等额外约束;不能直接把本例当作 CDN 配置。

一份缓存要过四道判断

问题 看什么 不能混淆的概念
能否保存 请求方法、状态及存储限制等 能传输不等于能保存
是否是本次请求的表示 方法、目标 URI、Vary 指定字段 同一 URL 也可能有不同语言版本
是否仍新鲜 freshness_lifetime 与 current_age 还在内存里不代表新鲜
需要验证时怎样处理 验证器、条件请求及响应 304 不提供新正文,但需要已有正文

RFC 9111 §4列出了复用的前提。因此,单看文件名、磁盘上有没有文件或 LRU 是否命中,都不足以决定 HTTP 的复用行为。

这张图省略了显式允许过期复用等分支,不表示 RFC 中所有过期响应都必然走同一条路径。

max-age 给寿命,Age 不是此刻的完整年龄

设响应包含 Cache-Control: max-age=60。这里的 60 是新鲜寿命,不是“无论何时收到都再保鲜 60 秒”。响应到达本地前,可能已经在源站或上游缓存经历时间。

按 RFC 9111 §4.2.3,用同一套秒单位与时间基准记。这里采用同时考虑 Date 与 Age 的较保守计算:

1
2
3
4
5
6
apparent_age = max(0, response_time − date_value)
response_delay = response_time − request_time
corrected_age_value = age_value + response_delay
corrected_initial_age = max(apparent_age, corrected_age_value)
resident_time = now − response_time
current_age = corrected_initial_age + resident_time

request_time 是发出请求的时刻,response_time 是收到响应的时刻;date_value 来自 Date,age_value 来自 Age,缺少 Age 时按 0 处理。Date 与本地时钟偏差也会影响 apparent_age。规范也允许直接把 corrected_age_value 用作 corrected_initial_age;若路径上存在可能不正确写入 Age 的旧缓存,可取两者最大值。本文始终使用后一种较保守方式,并不把它说成唯一允许的实现。

新鲜判断是 freshness_lifetime > current_age,相等时已经过期。本例私有缓存的寿命取 max-age;更完整的选择顺序、Expires 与启发式规则见 §4.2.1。

一条时间线算出为什么还剩十八秒

取请求发出时刻 100、收到响应时刻 104、响应 Date 对应 90、Age 为 12,当前时刻 130,max-age 为 60。这里用相对秒代替真实 HTTP 日期,便于手算;实际 Date 字段仍需使用 HTTP 日期格式。

Date为90,请求100发出、104收到,Age为12,当前130;校正初始年龄16加本地驻留26得到当前年龄42,寿命60还剩18秒。
原创合成时间线,刻度为同一相对秒基准,不是实测请求。Age 要连同请求耗时与 Date 一起校正;加上本地驻留时间后再判断新鲜度。查看原图
量计算结果(秒)作用
表观年龄max(0,104−90)14Date 提供的年龄估计
校正 Age12+(104−100)16计入请求响应耗时
校正初始年龄max(14,16)16收到时的年龄估计
本地驻留130−10426收到之后经过的时间
当前年龄16+2642与寿命比较
新鲜余额60−4218仍满足60>42

若只算 Age 12 加驻留 26,会得到 38,漏掉了本例的请求耗时。若从收到时重新计满 60,又会错把已有的 16 秒年龄抹掉。保持其他条件不变,到时刻 148 时 current_age=60,恰好过期。

五个常见指令,各自管哪一道门

以下讨论响应中的未带字段名限定的指令,依据 RFC 9111 §5.2.2。

响应指令 本文私有缓存里的含义 典型误读
max-age=60 明确新鲜寿命为60秒 收到后无条件再计60秒
no-cache 可以保存;复用前必须成功验证 等于不保存
no-store 不保存本次请求/响应的相应信息 只是每次验证一下
must-revalidate 过期后,成功验证前不得复用;断连也不能忽略 只要带它,每个新鲜响应也要验证
private 共享缓存不得保存该响应;私有缓存仍需满足其他条件 所有缓存都禁止保存

no-store 不是安全擦除机制,也不使过去已经合法保存的所有旧副本自动消失。no-cache 则关心复用前的验证。对于过期复用,规范还有显式授权与扩展机制;本篇教学模型统一不实现离线过期回退。

ETag 与 304 怎样一起工作

初次获得表示的报文可以包含:

1
2
3
4
5
6
HTTP/1.1 200 OK
Cache-Control: max-age=60
ETag: "v1"
Vary: Accept-Language

<已有页面正文>

后续需要验证时,请求携带已保存的验证器:

1
2
3
GET /guide HTTP/1.1
If-None-Match: "v1"
Accept-Language: zh-CN

服务器判断所选表示与条件匹配时,GET 可得到 304;不匹配则通常得到包含新表示的 200。这里验证的是所选表示,不是根据 ETag 的字面名称猜版本先后。

对 GET/HEAD,If-None-Match 使用弱比较;W/"v1" 与 "v1" 的 opaque-tag 相同即可匹配。它与要求强比较的 If-Match 不可混用。请求同时带 If-None-Match 与 If-Modified-Since 时,前者存在就忽略后者,见 RFC 9110 §13.1.2 与 §13.1.3。

已保存正文和ETag之后发送条件请求;304为选中的已有表示更新适用元数据并保留正文,200提供新正文,no-store限制存储。
原创状态示意。只有已正确选中且按验证器规则匹配的缓存表示,才能应用 304 更新;图不省略这个前提。查看原图

收到 304 后,缓存按 RFC 9111 §4.3.4识别应更新的已存响应,再按 §3.2更新适用字段。新提供的 Date、ETag、Cache-Control 等需要按规则合并;不能无条件更新同 URL 的任意条目,也不能把全部旧头清空。

304 本身不带表示正文,RFC 9110 §15.4.5定义了它的语义。客户端继续使用相应已有正文;年龄则结合本次验证响应重新计算,不能简单写成“304 后年龄必为 0”。例如验证请求160发出、164收到、Date为160、Age为0,收到时校正年龄就是4秒。

Vary 让同一地址有多个候选

若保存响应含 Vary: Accept-Language,下一次请求选择候选时还要比较该字段。zh-CN 的正文不能仅因 URL 一样就拿去满足 en 请求。头字段缺失与存在也是不同情况,具体匹配还要考虑规范允许的归一化;本篇内部模型只使用已经规范化的字符串。

Vary 是响应对选择条件的声明,并不是让缓存自动理解“这些正文差不多”。Vary: * 表示无法以这种匹配方式找到可直接复用的存储响应,详见 §4.1。隐私隔离、认证身份和缓存分区仍有各自规则,Vary 不能代替完整的权限设计。

边界、自检与内部核验

场景 应检查的结果
current_age 恰好等于 max-age 已经过期
条目新鲜但响应有 no-cache 仍需验证
条目新鲜且仅有 must-revalidate 该指令本身不额外强制此刻验证
过期且 must-revalidate,源站不可达 不能擅自复用过期响应
304 但没有能识别的已有表示 不能凭空产生正文
200 提供新表示且带 no-store 可以处理本次响应,但不把它作为新缓存条目保存
Vary 字段不匹配 不能使用这一候选满足请求
自检:缓存命中且返回给应用的页面来自本地正文,是否就说明没有网络请求?

不一定。条件验证可以经过网络得到304,再复用已有正文。“没有重新传正文”与“没有发送请求”是两个判断。

自检:收到304,能否把任意旧副本的年龄设为0?

不能。先按请求选择与验证器规则识别已有表示,再更新适用元数据。年龄包含本次响应的Date、Age和请求耗时,收到时也可能已经大于0。

tests/verify-http-cache-article.cjs 检查本文的时间算术与限定状态模型:独立时间轴公式、严格新鲜边界、指令与变体组合、条件匹配和已选条目的304/200更新。模型只对单一已选候选、同一强 ETag 演示304更新;弱比较另外检查,不实现弱验证器与多个候选的完整更新选择规则。它没有实现完整缓存产品,也没有把教学测试称为浏览器或 CDN 的协议认证。

回到 LRU 的复用距离,可以把两个问题合在一张笔记上:容量策略决定条目有没有被留下;HTTP 规则决定这个条目此刻能怎样使用。再结合网络请求过程,就能把直接复用、条件验证和完整下载放进同一条请求链路。