先给结论:多层缓存返回不同版本,通常不是“某层坏了”,而是某一层按自己的规则存了不同副本,而你的验证请求恰好命中了不同层。要定位它,最有效的动作是让同一请求带上可识别的标记,分别观察每一层的响应,而不是反复刷新页面看快慢。快慢只是表象,版本差异才是可核对的证据。
假设一个场景:源站更新了页面,浏览器看到的是旧价格,CDN 返回的是新价格,而服务器日志显示源站已经输出了新版本。直觉会认为“缓存命中等于更快更一致”,但这里出现了相反结果——命中越多,版本越乱。这类现象说明,速度指标和版本一致性是两个独立维度,不能用加载时间推断内容是否正确。
另一个常见反常是:清空浏览器缓存后页面正常,但真实用户仍然看到旧内容。这说明问题不在最靠近用户的层,而在中间某一层保留了旧副本。此时继续在前端调优没有意义,因为需要先确定是哪一层在返回旧版本。
第一种解释是内容分发层保留了旧副本。源站已更新,但中间缓存尚未过期,或者过期后回源拿到了另一份响应。它的特征是:同一 URL 在不同网络、不同地区返回不同版本,且差异与请求路径相关,而不是与页面本身相关。
第二种解释是键值设计导致版本错配。缓存按 URL 加查询参数、Cookie、请求头等组合成键。如果某些请求带 ?v=2、某些不带,或者移动端与桌面端共用了一个键,就可能出现“同一页面被拆成多个版本、彼此互不覆盖”的情况。它的特征是:版本差异与请求特征稳定对应,同一特征组合下结果一致。
两种解释都表现为版本不一致,但成因不同:前者是时间问题,后者是分组问题。区分的意义在于,处理动作完全不同——前者要等过期或主动失效,后者要改键值规则。
能区分这两种解释的证据,是响应头中的缓存状态字段和版本标识。具体动作:对同一个 URL 发起多次请求,分别带上唯一查询参数、不同 Cookie、不同请求头,记录每次返回的响应头与正文中的版本标记。然后对比三件事——命中状态、缓存年龄、以及正文版本。
需要提醒的是,请求量或抓取量归零、某个路径突然没有日志,都不能单独证明处理正确。它们也可能是采集方式变化、请求被合并或日志采样造成的。把这些现象当作唯一证据,容易得出错误结论。
假设某页面在源站标记为版本 B,但部分用户看到版本 A。可以设计一组对照:请求一不带任何参数,请求二带一个从未使用过的随机参数,请求三带与旧版本相同的参数。分别记录响应头中的缓存状态和正文版本。
如果请求二稳定返回版本 B,而请求一和请求三返回版本 A,说明缓存键对参数敏感,旧参数命中了旧副本——这更接近键值设计问题。如果三个请求都返回版本 A,且响应头显示缓存年龄较大,则更接近中间层未过期。这个例子的数字只是说明比较方法,不代表任何真实项目的观测结果。
得到这个结果后,下一步动作取决于结论:键值问题需要调整缓存键规则并重新验证;未过期问题需要确认失效机制是否覆盖了所有层。无论哪种,都应先用对照请求复测,再决定是否改动源站,避免在错误层上反复调整。
建议的验证顺序是:先固定请求特征,再逐层比对响应,最后才考虑改动缓存策略。顺序颠倒会导致“改了源站但问题仍在”,因为旧副本还在中间层。
几个容易踩的误区:把清空浏览器缓存当作全局清缓存;把某一次的快速响应当作一致性证明;把 robots.txt 的抓取限制当作索引移除手段——它控制的是抓取,不是缓存版本。站点地图也不保证收录,HTTPS 也不保证内容一致或无漏洞。不同搜索引擎和中间缓存对缓存头的支持情况需要分别核查,不能凭一次观测下结论。
最终判断标准只有一个:同一请求特征下,各层返回的版本是否可复现地一致。只要还存在无法用请求特征解释的版本差异,就说明还有一层未被纳入观察,需要继续收窄范围。