SakuraCat设备线路说明
订阅决策

看到缓存命中,为何仍可能拿到不同内容:Age、新鲜度与重新验证怎么读

缓存命中只说明响应由缓存参与提供;内容是否仍可复用取决于当前年龄、新鲜度寿命、请求指令、验证器和共享或私有缓存范围。

接口日志显示“缓存命中”,页面却仍可能与同事看到的版本不同。缓存命中只说明某个缓存找到了可复用对象;能否复用、对象是否仍新鲜、是否经过重新验证,还要看请求条件与响应元数据。

命中不是内容相同的同义词

RFC 9111 规定,缓存复用不仅要匹配目标 URI 和方法,还要满足 Vary 指定的请求字段,并且响应仍新鲜、被允许以陈旧状态发送,或已经成功验证。语言、压缩、认证状态等请求条件不同,可能落到不同对象;两个使用者都看到 HIT,也不代表命中了同一份表示。

先记录完整请求条件:URL、方法、关键请求头、登录状态、观察时间和所在网络。只截图一个 HIT 标签,会丢掉决定对象选择的上下文。

Age 记录年龄,不是发布日期

RFC 9111 用 current_age 与 freshness_lifetime 比较:前者较小时,响应仍新鲜。Age 估算对象自源站生成或上次成功验证以来经过的秒数,并包含缓存驻留和传输造成的年龄。

Age 为 120 不能直接读成“页面两分钟前更新”,它只描述当前缓存链计算出的年龄。对象被重新验证、清除或驱逐后,Age 可能重置;不同缓存节点也可能拥有不同存放时间。

受控比较中,甲响应 Age 较大但 max-age 更大,仍可新鲜复用;乙响应 Age 较小,但有效期更短,已经需要验证。只比较 Age 数字大小,会把“年龄”和“是否过期”混为一谈。

新鲜度由明确规则决定

共享缓存通常优先读取 s-maxage,再看 max-age 或 Expires。没有明确期限时,只有在协议允许的响应上才能启发式估算。应用若希望不同层采取不同期限,就要明确区分浏览器与共享缓存策略。

“仍保留在缓存里”和“无需联系源站即可使用”也不是同一状态。Cloudflare 文档把 retention 与 freshness 分开:对象可以继续留在缓存中,但已过期,需要重新验证后才能正常复用。

所以排查时至少记录 Cache-Control、Expires、Date、Age、ETag 或 Last-Modified,以及平台缓存状态。缺少其中一项,结论就应标明范围,不能补猜。

重新验证可以继续使用旧正文

过期对象若有 ETag 或 Last-Modified,缓存可发送条件请求。源站返回 304 时,表示已存表示仍适用,缓存更新元数据并继续复用原正文。使用者看到的正文没有重新下载,不代表缓存绕过了源站;它可能刚完成一次成功验证。

反过来,验证失败、超时或策略允许 stale-while-revalidate 时,缓存可能暂时发送陈旧正文。平台状态可能显示 REVALIDATED、UPDATING 或其他实现标签,必须结合时间线解释。

一个清楚的时间线应写:何时源站发布新版本、何时对象首次进入各节点、何时过期、何时发起条件请求、验证结果是什么。没有源站发布时间时,只能描述缓存观察,不能断言源站当时已有新内容。

为什么两台设备可能不同

设备可能访问不同边缘节点,带有不同 Cookie 或 Accept-Language,也可能一台从浏览器缓存复用,另一台从共享缓存取得。服务端若按 Vary 分流,这些差异是设计结果,不一定是故障。

排查时先用相同未登录条件和相同请求头做对照,再逐项加入语言、编码或身份条件。若直接比较一个登录浏览器和一个命令行请求,差异来源太多。

还要区分 HTML 与图片、脚本等子资源。页面正文已验证,不代表图片对象同步失效;每个 URL 有自己的缓存键和期限。把整页概括成一个“缓存版本”,会漏掉资源层差异。

看到缓存命中,为何仍可能拿到不同内容:Age、新鲜度与重新验证怎么读 配图 1
看到缓存命中,为何仍可能拿到不同内容:Age、新鲜度与重新验证怎么读 配图 1

四步判断一条缓存响应

第一步确认请求是否选择同一缓存键,包括 URI、方法和 Vary 条件。第二步用 Age 与 freshness_lifetime 判断当时是否新鲜。第三步检查是否发生条件验证,以及 304 或新 200 如何更新对象。第四步记录边缘节点、浏览器层和观察时间,解释为什么不同设备可能不同。

若结果要交给同事,保存原始响应头和时间,不要只写“清缓存后好了”。清除会改变证据本身;在操作前先保存基线,才能说明修复影响哪一层。

结论停在缓存链

缓存命中、Age 很小或状态显示已验证,都只描述缓存链的一部分。它们不能单独证明内容是最新业务版本,也不能证明所有地区与设备看到相同表示。

稳妥结论会写明:哪一个请求条件、在哪个节点、何时取得哪组响应头,按什么新鲜度规则复用,以及是否成功验证。只有把这些字段放回时间线,命中状态才从标签变成可解释证据。

资料依据:RFC Editor《RFC 9111: HTTP Caching》;Cloudflare《Retention vs Freshness (TTL)》与《Cloudflare cache responses》。来源用于解释协议和平台状态,不代表任何具体站点的故障归因。

Date、Age 与本地时钟不能混读

响应的 Date 由服务器生成,Age 由缓存链计算,抓取工具记录的本地时间来自观察设备。三者若时钟不同,会出现看似负数或跳动的间隔。排查时统一保存时区,并把本地接收时间视为观察证据,不用它改写响应头的含义。

经过多个缓存时,Age 还会反映上游缓存已有年龄。下游节点刚收到对象,不代表内容刚从源站生成。若只看当前节点驻留时间,就可能低估对象在整条链上的年龄。

缓存键还可能包含实现规则

协议列出 URI、方法和 Vary 等基础条件,平台还可能把查询参数、设备类别或自定义键纳入。遇到“同 URL 不同内容”时,要同时查看站点声明的缓存键规则。没有配置访问权,就明确写无法确认,不以经验猜测。

查询参数顺序、默认值和追踪参数是否进入缓存键,也会影响命中。一次带随机参数的请求显示 MISS,不能与没有参数的 HIT 直接比较。先规范比较条件,再解释状态。

看到缓存命中,为何仍可能拿到不同内容:Age、新鲜度与重新验证怎么读 配图 2
看到缓存命中,为何仍可能拿到不同内容:Age、新鲜度与重新验证怎么读 配图 2

处理 stale 时写清楚失败模式

允许陈旧内容通常是为了在源站暂时不可用时维持服务。它带来可用性,也扩大内容与源站不同步的窗口。报告要说明是验证进行中、源站错误、明确 stale-if-error 策略,还是平台后台刷新,不能把所有陈旧发送都写成“缓存坏了”。

如果内容涉及价格、安全公告或撤回通知,业务对陈旧的容忍度可能低于普通图片。协议允许不等于业务适合;缓存策略应按内容风险设定,并保留紧急清除和验证路径。

用两组请求做最小对照

第一组保持相同 URL 与请求头,在短时间内重复,观察 Age、状态与验证器变化。第二组只改变一个 Vary 条件,例如语言,再比较对象是否分开。若同时换设备、网络、登录和语言,结果无法定位。

每次对照都保存完整头,不只摘录 Cache-Status。状态标签是平台摘要,协议字段才让读者重算新鲜度。平台标签与协议看似不一致时,先核对是否观察了浏览器、边缘或上游中的不同层。

最后把结论写成条件句:“在相同请求条件和这个边缘节点,响应于某时仍新鲜/已验证/被允许陈旧发送”。条件句比“缓存是旧的”更长,却能让下一位排查者直接复现。

提交排查记录前,再把关键响应头按原样附上,并写明抓取命令或工具版本。手工抄写可能遗漏大小写、引号或多个 Cache-Control 指令;原始证据让下一位读者可以重新计算年龄和有效期。

如果缓存策略由多层配置共同决定,分别列出浏览器、边缘与源站控制,标明哪一层已经确认、哪一层没有权限查看。不要用某一层的标签替其他层下结论。

当同一问题涉及多个资源时,选择一个 HTML 和一个关键静态资源做代表对照,再根据结果扩大范围。一次抓取整页所有资源会产生大量状态,却不一定更快定位。

资料来源

  • RFC Editor:《RFC 9111: HTTP Caching》,发布或更新于 2022-06-01
  • Cloudflare:《Retention vs Freshness (TTL)》,发布或更新于 2026-05-06
  • Cloudflare:《Cloudflare cache responses》,发布或更新于 2026-07-23