先给结论:不要从最外层缓存逐层清,而是先固定一个可复现的请求,再沿链路比对同一资源的版本标识与响应头,找出第一个版本发生分歧的节点。多数“多层缓存不一致”不是缓存本身出错,而是各层用了不同的缓存键或不同的 Vary 条件,导致同一 URL 被当成多个对象存储。
你手里通常是一个页面或一份资源,表现为有时返回旧内容、有时返回新内容。第一步不是改配置,而是把它变成一条固定命令:固定 URL、固定请求头、固定来源 IP 或出口,连续请求若干次并记录每次的响应头与正文摘要。
关键动作是给每次请求打上可区分的标记,例如在测试环境用查询参数区分,或用一个自定义请求头标识本轮。这样做的结果是:你能判断“不同版本”是按请求随机出现,还是按来源、按时间、按路径出现。前者指向缓存键设计问题,后者指向过期策略或回源问题。这一步的结果直接决定下一步查哪一层。
正文差异可能来自压缩、编码或时区格式化,不能直接当作版本不同。应优先比对三类可区分证据:
这里要提醒一个常见误判:某个统计或抓取量归零,不能单独证明处理正确。它也可能是采集窗口调整、请求被合并或日志采样造成的,需要结合版本标识一起看。
把链路画成有序节点:客户端本地缓存 → 中间反向代理 → CDN 边缘 → 源站应用缓存 → 数据库或对象存储。从最靠近源站的一端开始,逐段请求并记录版本标识,找到第一个与源站不一致的节点,问题就在它或它前面一层。
假设一个例子:源站返回 ETag 为 A,CDN 边缘返回 A,但反向代理返回 B。这说明反向代理持有的对象与源站不同,可能是它的缓存键忽略了某个影响内容的请求头,或它比源站更早缓存了旧版本。此时清空反向代理并复测,如果版本恢复一致,就应回头检查它的缓存键与 Vary 配置,而不是继续清 CDN。这个动作的结果是缩小范围,避免无差别刷新所有层。
多层缓存最容易出错的地方是各层对“同一资源”的定义不同。比如源站按 Accept-Language 返回不同语言版本,但某一层缓存键只取 URL,忽略该头,于是把中文版回放给英文请求。逐层核对缓存键包含哪些维度:路径、查询参数、Host、Vary 指定的头。任何一层缺一个维度,都会产生版本分歧。
不同层对 max-age、s-maxage、must-revalidate 的处理可能不同,尤其当中间层自行改写这些头时。若某层在过期后仍直接回放旧对象而不回源再验证,就会出现长期不一致。可临时把某层设为不缓存或强制再验证来验证假设,但这是诊断手段,不是长期方案。
定位到分歧节点后,按以下顺序处理:
最后一点边界:如果资源本身依赖 robots.txt 或站点地图来引导抓取,要记住抓取限制不等于可靠的索引移除,站点地图也不保证收录;它们与缓存一致性是两个层面的问题,不要用改 robots 来掩盖版本分歧。