先做一件事:把出现版本分歧的那个URL,在每一层缓存上分别取一次响应头,记录Age、Cache-Control、ETag或Last-Modified、Vary和实际正文的指纹。只要你能把同一时刻、同一URL在各层的这些字段摆在一起,一致性问题的定位就从猜测变成了比对;而多数情况下,分歧会集中出现在其中一层,而不是均匀分布在所有层。
多层缓存返回不同版本,表现可以分成两类,处理顺序完全不同。
Age很大、Cache-Control与源站不一致,或Vary字段在某一层被改写。这类分歧说明缓存链路上有中间层在重写头部,正文一致不代表问题不存在,它会影响后续所有层的判断依据。判断方法是计算正文的哈希值而不是肉眼看。把各层返回的正文做同一算法的摘要,摘要一致就归入第二类。这一步的实际价值在于:如果正文一致,你就不必再追内容同步,而应转向检查头部改写规则,排查范围会明显收窄。
常见的错误做法是反复刷新浏览器、换设备、换网络去“看有没有变”。这样得到的是不同时刻、不同路径的混合结果,无法定位是哪一层的问题。更可行的做法是固定一条请求路径,逐层向上取样本。
curl -I或等效方式直接请求源站,记录响应头与正文摘要,作为基准。每一步只改变一个变量,得到的结果才有比较意义。如果第2步与第1步不一致,而第3步与第1步一致,问题就在CDN这一层;如果第1步本身在短时间内两次请求就不同,那说明源站或应用层没有稳定输出,缓存只是把不稳定的结果放大了。
确认分歧所在层之后,通常有两种做法,选择取决于分歧是否可复现。
策略一:先执行清除,再观察是否复发。适用于分歧只出现一次、且能确认某次发布或配置变更与它时间接近的情况。代价是清除操作本身可能只覆盖部分节点,或需要一定时间传播;如果问题复发,你等于消耗了一轮时间却没有拿到新证据。执行清除后,下一步应立即记录清除前后的Age与摘要变化,用它判断清除是否真正生效,而不是凭页面看起来对了就收工。
策略二:先不动缓存,改为固定缓存键与过期规则。适用于同一URL在短时间内反复出现多个版本的情况。此时清除只是暂时掩盖,根因更可能是缓存键设计不一致,例如某一层把查询串纳入键、另一层忽略查询串,或者Vary在某层被剥离,导致同一份缓存被不同请求复用。代价是调整规则需要发布并等待生效,期间分歧可能继续存在,因此适合能接受短暂不一致、但要求彻底收敛的场景。
选择条件可以简化成一条:分歧可复现且与请求特征相关,选策略二;分歧一次性、与某次变更时间吻合,选策略一。两者并不互斥,但先做哪一个,决定了你接下来拿到的是“已恢复”还是“已定位”。
假设某页面源站返回Cache-Control: max-age=600,CDN层配置为max-age=3600,应用层页面缓存为300秒。这三个数字本身不构成错误,但组合起来会产生一个可预期的现象:发布新内容后,源站已更新,应用层在300秒后更新,CDN边缘仍可能在最长一小时内返回旧版本。此时如果你只查源站,会认为一切正常;只查浏览器,会认为缓存没清干净。
可执行的动作是:把这三处的过期时间与缓存键规则列在同一张表里,找出最短与最长的差距,然后决定是把各层对齐到同一量级,还是接受分层过期但把清除流程覆盖到最长的那一层。这个动作的结果会直接决定下一步——如果对齐后分歧消失,问题属于配置不一致;如果对齐后仍然存在,就应转向检查是否有未纳入清单的第四层缓存,例如运营商缓存或应用内部的片段缓存。
定位完成后,至少留下三项内容:出现分歧的URL、各层在同一时间点的响应头与正文摘要、以及你采取的动作和时间。这样做的原因是,缓存一致性问题很容易在清除后暂时消失,如果没有记录,下次复发时你无法判断它是同一个原因还是新原因。记录中不需要判断谁对谁错,只需要让下一次比对有基准。当同一现象第二次出现时,先比对新旧记录中的Age与缓存键字段,再决定是否重复清除操作,这比重新从零排查要快得多。