先给结论:当同一网址经过多层缓存后返回不同版本,不要从“哪一层坏了”开始查,而要先确认这些版本是否对应同一个缓存键和同一份源站响应。定位顺序应是固定请求条件、逐层比对响应头与正文指纹、再回到源站验证。下面用一个假设情境串起决策过程。
假设某站点在边缘缓存、反向代理缓存和源站之间串了三层。运维在边缘节点请求 /product/a,拿到的是旧价格;在代理层请求同一路径,拿到的是新价格;直接回源,又是旧价格。此时最容易得出的结论是“边缘缓存没刷新”,但这个结论并不成立,因为三次请求可能根本没有命中同一个缓存对象。
需要先固定变量:请求方法、协议、主机名、路径与查询串、Accept-Encoding、Cookie、User-Agent、Vary 相关头,以及是否带 Cache-Control: no-cache。任何一项不同,都可能让某一层返回另一个变体。规模化后出现例外,往往不是缓存整体失效,而是某类请求头或某个查询参数把流量导向了不同键。
逐层抓取响应头,重点看 Age、Cache-Control、ETag、Last-Modified、Vary、X-Cache(若存在)以及响应正文的哈希。若边缘层与代理层的 ETag 相同而正文不同,说明中间层可能改写了内容;若 ETag 不同,则更像命中了不同源版本或不同变体。
一个可操作的判定动作是:用同一组请求头分别请求三次,并记录每层的 Age 与正文指纹。如果 Age 在增加而正文不变,说明该层只是复用了旧对象;如果 Age 归零但正文仍是旧版,问题更可能在回源链路或源站生成逻辑上,而不是缓存层本身。
这里要提醒一个边界:请求量、抓取量或某层命中数突然归零,不能单独证明缓存已正确处理。它也可能来自流量下降、探测路径改变、鉴权失败或日志采样变化。归零只是线索,不是结论。
直接清空所有缓存会破坏现场,让“不同版本”变成“暂时一致”,但根因仍在。更稳的做法是按层记录:
Age、Vary、是否带 Set-Cookie。如果代理层回源时把 Accept-Encoding 或 Cookie 改写了,源站可能返回不同分支。此时边缘层看到的“旧版本”未必是旧缓存,而是源站对另一组请求条件给出的正常响应。定位一致性问题的关键,是找出哪一层改变了请求条件,而不是哪一层“没刷新”。
假设源站有两台应用实例,A 实例已发布新模板,B 实例仍加载旧模板。边缘缓存和代理缓存都正常,但回源时被轮询到不同实例,于是不同层看到不同版本。这种情形下,清缓存只能短暂掩盖问题,下一次回源仍会分裂。
可区分的证据是:在同一层连续回源多次,若正文指纹在两种值之间跳动,且 ETag 或模板版本号随之变化,就应优先查源站实例一致性、发布顺序和配置下发,而不是继续调缓存 TTL。反之,若回源结果稳定,只有某一层返回旧值,才回到该层的缓存键与刷新机制上查。
另一个常见原因是 Vary 设置过宽或过窄。过宽会让同一页面按大量请求头拆成多个变体,规模化后出现“有人看到新版、有人看到旧版”;过窄则可能把本应区分的移动端与桌面端、登录与未登录内容混在同一个键下。判断依据不是猜测,而是对比不同请求头组合下返回的正文指纹是否与预期变体一致。
确认根因后,选一个最小对照:固定同一组请求头,只改变一个变量,例如只刷新边缘层,或只重启一台源站实例。观察下一轮请求中各层返回的 Age、ETag 与正文指纹是否收敛到同一值。若收敛,再逐步扩大请求样本;若仍分裂,说明还有未识别的变体或未同步的源站实例。
这个动作的结果会直接决定下一步:如果只刷新边缘层就一致,说明问题在边缘缓存键或刷新范围;如果必须同时同步源站实例才一致,说明缓存只是放大器,真正要修的是发布与配置流程。对网址收录而言,只有当爬虫在稳定条件下拿到一致响应,收录判断才有可靠前提;否则你看到的“已收录”或“未收录”可能只是命中了不同缓存变体。