先给结论:不要从“哪个版本是对的”开始查,而要先确认同一请求在每一层缓存上是否真的命中了同一个变体。多层缓存返回不同版本,通常不是缓存坏了,而是各层的缓存键、TTL 和失效传播范围不一致。定位顺序应是:固定请求指纹 → 逐层比对响应 → 找出首个分叉点 → 再决定保留、改写还是退出该层缓存。
多层缓存下,同一 URL 可能因为 Cookie、Accept-Encoding、User-Agent、查询参数顺序不同而落到不同变体。你需要在每一层用完全相同的请求指纹复现,否则比对的是两个不同的缓存对象。
?a=1&b=2 与 ?b=2&a=1 被当成两个键。Accept-Encoding 和 Vary 涉及的字段。Age、Cache-Control、ETag、Last-Modified、X-Cache 一类自定义标记。如果请求指纹不固定,你会看到“不同版本”的假象,实际是不同变体。这一步没做,后面的比对没有意义。
把链路拆成浏览器缓存、CDN 边缘、反向代理、应用内缓存、源站。从最靠近用户的一层开始,逐层向后请求,记录每层返回的正文哈希与关键响应头。第一个与下一层不一致的位置,就是分叉点。
常见分叉原因有三类,可用证据区分:
Age 差异明显,且失效后仍有一层返回旧内容。Age 持续增长。这里要说明一个边界:请求量、抓取量或某个统计归零,不能单独证明缓存处理正确。它也可能是流量下降、抓取预算变化或监测点本身被缓存。需要结合响应头和正文哈希一起判断。
适用前提:该层缓存键与上游一致,TTL 可控,且失效接口能覆盖全部变体。此时分叉只是配置疏漏,修正键或失效范围即可,不必移除缓存。动作是统一各层的 Vary 与 TTL,然后重新逐层比对,确认分叉点消失。
适用前提:该层无法精确控制键,但可以调整缓存粒度。例如把带用户态的响应改为不缓存,只缓存公共部分。动作是给个性化响应加 Cache-Control: private 或 no-store,再观察下游是否仍返回混合版本。改写后如果分叉点前移,说明问题被推到了更靠近源站的一层,需要继续排查。
适用前提:该层既不支持所需键,也无法及时失效,且业务对版本一致性要求高于回源成本。此时退出是合理选择。动作是让该层直接回源或绕过缓存,再确认一致性是否恢复。代价是回源压力上升,需要评估源站能否承受。
注意:robots.txt 的抓取限制不等于可靠的索引移除;站点地图不保证收录。这些与缓存一致性是不同层面的问题,不要混在一起处理。
假设链路为浏览器 → CDN → 反向代理 → 源站。同一 URL 在 CDN 返回版本 A,在代理返回版本 B,源站为版本 B。逐层比对后,首个分叉点在 CDN。
进一步检查 CDN 缓存键,发现它把某个 Cookie 计入了键,而代理没有。带该 Cookie 的请求在 CDN 命中旧变体,不带则命中新变体。修正方式是让 CDN 与代理使用相同的键规则,或对该 Cookie 设置不参与缓存。修正后重新比对,若 CDN 与代理都返回版本 B,分叉点消失;若仍不一致,则继续向源站方向排查。
这个例子的数字和层级都是假设,用于说明比较方法,不代表任何真实平台行为。实际排查时,应以你自己链路的响应头和正文哈希为准。
定位一致性问题的核心不是找“正确版本”,而是找“首个分叉点”。分叉点确定后,再根据该层是否可控、失效是否完整、业务对一致性的要求,决定保留、改写还是退出。每做一个动作,都要重新逐层比对一次,用响应头和正文哈希确认分叉点是否前移或消失。如果分叉点前移,说明你处理的是下游层,问题仍在更靠近源站的位置,需要继续排查。