先给结论:当同一入口在不同层返回不同版本时,不要从“谁对谁错”入手,而要先判断差异是时间差造成的,还是键设计不一致造成的。前者会随缓存过期自行收敛,后者不会。区分方法只有一个:在固定时间窗口内,对同一请求键连续取多层响应并记录版本标识,看差异是收敛还是稳定复现。稳定复现的,才是需要改代码或改配置的一致性问题。
多层缓存通常包括浏览器缓存、CDN 边缘节点、反向代理层和应用内缓存。它们各自有独立的 TTL 和淘汰策略,因此“不同版本”有两种完全不同的成因。
解释一:过期节奏不同。同一份内容在上游已更新,但某一层仍持有旧副本,直到该层 TTL 到期。这种情况下,各层版本号会按时间顺序依次追平,差异是暂时的。
解释二:缓存键设计不一致。例如边缘层按完整 URL 缓存,应用层却按去掉查询参数的路径缓存,或者两层对 Vary 头的处理不同。结果是同一逻辑资源被存成多份,且各层可能长期返回不同版本,不会自行收敛。
这两种解释对应的处理动作完全不同:前者只需等待或主动刷新,后者必须改键规则。判断错方向,会把时间问题当 bug 修,或者把真实的键冲突当成“再等等就好”。
假设某列表页在应用层已发布新版本,版本标识从 v41 变为 v42。构造一个可复现的探测:对同一个请求键,在 10 分钟内每隔 1 分钟依次请求边缘层、代理层和应用层,记录每层返回的版本标识。这个例子只是说明比较方法,不代表任何真实项目的观测结果。
关键证据不是某一时刻的快照,而是差异是否随时间收敛。单次抓到不同版本,既可能是时间差,也可能是键冲突,不足以定性。
确认属于键冲突后,下一步是找出差异跟着什么变化。保持上游内容不变,只改变请求的某一项特征,观察哪一层跟着变。
Vary 的处理与其他层不同。哪一项特征能稳定地把版本分成两组,那一项就是缓存键差异的来源。这一步的价值在于:它把“多层缓存不一致”这个模糊现象,缩小到一个具体的键字段上,后续改动才有明确目标。
如果证据指向时间差,动作是统一各层的 TTL 预期,并在内容更新后按层主动刷新,而不是改键规则。做完这一步,重新跑同一探测序列;若差异在预期时间内收敛,问题闭环,不需要动代码。
如果证据指向键冲突,动作是先统一缓存键的定义,再让各层按同一规则生成键。改完后同样重跑探测序列,并额外验证:改变此前能区分版本的请求特征时,各层是否仍返回同一版本。如果此时各层一致,说明键已对齐;如果仍有分层,说明还有未覆盖的键字段,需要回到上一步继续缩小范围。
需要注意一个反直觉的点:某一层请求量或命中量降到接近零,并不能单独证明该层已被正确绕过或问题已解决。它也可能是流量整体下降、探测方式改变或该层被临时摘除所致。判断处理是否有效,仍要回到版本序列是否收敛这个证据上。
与其每次靠人眼比对,不如把探测固化成固定流程:确定一个请求键,记录各层版本标识,在固定时间窗口内重复采样,先看收敛性,再看差异与哪项请求特征绑定。这样每次内容更新或配置变更后都能复跑,一致性问题的定位就不再依赖临时猜测。
另外,若排查涉及抓取与索引层面的现象,要分清边界:robots.txt 的抓取限制不等于可靠的索引移除,站点地图也不保证收录,这些与缓存版本一致性是不同层面的问题,不要用缓存层的结论去推断索引结果。