先给结论:当同一 URL 经多层缓存后返回不同版本,最可能的一致性问题不在蜘蛛本身,而在某一层缓存的键(cache key)没有包含决定内容版本的那个变量——常见的是 Cookie、设备类型、地域、查询参数或回源时的 Vary 头。定位方法不是逐层清缓存,而是固定一个请求特征,逐层比对同一请求在各层拿到的响应体、响应头和缓存命中标记,找出第一层开始分叉的位置。若各层对同一请求始终返回同一版本,只是不同请求拿到不同版本,那么问题属于缓存分片而非缓存失效,结论要换成另一种处理。
多层缓存通常包括 CDN 边缘、中间反向代理、应用层对象缓存,以及数据库或模板层缓存。跨层比对的前提是请求特征完全一致:同一 URL、同一方法、同一组请求头、同一来源 IP 段、同一时间窗。只要其中一项变化,返回不同版本就可能是正常分片,而不是故障。
实际操作上,先构造一个最小请求,例如只带 Host、User-Agent 和一个用于区分身份的最小 Cookie。然后按边缘到回源的顺序,在每一层记录三样东西:响应体的关键差异字段(如价格、登录态、canonical 链接)、响应头中的缓存相关字段(Age、Cache-Control、Vary、命中标记)、以及该层的缓存键说明。哪一层开始出现与上一层不同的版本,分叉点就在那一层及其上游。
这一步的结果直接决定下一步:如果分叉点在最外层,问题多半是边缘缓存键缺了某个请求头;如果分叉点在最内层,问题更可能在应用自身的缓存键或数据读取逻辑。没有这个分叉点,后续所有清缓存动作都是盲试。
缓存键缺失的典型证据是:同一请求特征下,边缘返回的版本与回源直连拿到的版本一致,但不同请求特征之间版本互相串。也就是说,A 用户的请求命中了 B 用户的缓存对象。此时响应头里常能看到 Vary 未覆盖真正区分内容的头,或者缓存键里只用了 URL 而忽略了 Cookie。
回源内容本身在变的证据是:绕开所有缓存直连应用,同一请求短时间内多次返回不同版本。这可能来自应用层的读写竞争、灰度发布、模板缓存与数据缓存不同步,或后端多实例加载了不同配置。这种情况下,清缓存只能短暂掩盖,版本很快再次分叉。
一个假设例子:某页面在边缘缓存中返回未登录版,回源返回登录版,直连应用也返回登录版。若边缘缓存键只含 URL,那么登录版内容会被未登录请求命中并污染缓存对象。此时把 Cookie 加入缓存键或对登录态设 private,就能让分叉消失。这个例子的数字和字段仅用于说明比较方法,不代表任何真实站点配置。
如果各层对同一请求始终返回一致版本,但不同来源 IP、不同设备或不同查询参数拿到不同版本,那么这不是缓存一致性问题,而是有意的缓存分片或 A/B 分流。此时逐层比对不会发现分叉点,因为每层都“正确”地返回了它该返回的版本。继续按缓存键缺失去改,反而可能破坏正常分流。
要区分这两种情况,看响应头是否带有明确的分流标记、Vary 是否按设计覆盖了区分维度、以及不同版本是否在业务上本就该并存。若版本差异与请求特征一一对应且稳定,先怀疑设计,不要先怀疑故障。
各层访问日志能提供请求进入哪一层、命中还是回源、返回什么状态码。把同一请求 ID 或同一时间戳串起来,可以确认分叉发生在命中还是回源阶段。需要注意的是,抓取量或某层命中量归零,不能单独证明处理正确:它也可能是该层被绕过、日志采样、时间窗错位或请求被上游拦截造成的。要结合响应体和响应头一起判断。
robots.txt 的抓取限制不等于可靠的索引移除,站点地图也不保证收录,这两点在排查缓存版本问题时同样成立——它们不解决缓存键缺失,也不解释版本分叉。若问题涉及不同搜索引擎,其抓取与缓存行为须分别核查,不能拿一个引擎的表现推断另一个。
确定分叉层后,只改一个变量:补齐该层缓存键中缺失的请求特征,或对不该共享的内容设置不被公共缓存存储。改完立即用同一组固定请求复测,观察分叉点是否消失、响应头命中标记是否符合预期。若分叉点转移,说明还有第二层键不完整;若分叉消失但回源压力明显上升,说明缓存命中率被牺牲,需要评估是否用更细的分片键替代完全绕过缓存。整个过程以可复现的请求和逐层证据为准,不以单次抓取结果下结论。