先给有条件的结论:当同一 URL 在 CDN、反向代理和浏览器三层缓存中返回不同版本时,优先怀疑“缓存键不一致”,而不是内容本身出错。只要各层缓存键的构成不同——例如 CDN 含 Accept-Encoding、代理只看路径、浏览器还带 Cookie——同一请求就可能命中不同对象。这个结论成立的前提是:你能拿到每层实际返回的响应头并逐层比对。如果拿不到中间层响应头,下面的方法会失效,需要先想办法打通可观测性。
不同角色说“我看到的和你不一样”,往往不是缓存问题,而是观察口径不同。先做一件事:让每个人用同一 URL、同一请求方法、同一关键请求头(至少统一 Accept-Encoding 和 Cookie 状态),记录返回的 ETag、Last-Modified、Age、Vary 和响应体摘要。结果会直接决定下一步:
ETag 不同但内容摘要相同,问题在标识符生成,不在内容;Vary 缺失或与缓存键不匹配,问题在配置;Age 差异大而 ETag 相同,只是新鲜度不同,不是版本冲突。这一步的结果会告诉你该改配置还是该改观测方式,避免直接去清缓存。
多层缓存不一致,本质是各层对“什么算同一个资源”的定义不同。逐层列出缓存键包含的维度,通常能定位冲突:
Accept-Encoding、设备类型、地域,取决于配置;Cache-Control、Vary 和本地 Cookie 影响。假设某页面在 CDN 上按“路径 + 查询串”缓存,而代理只按路径缓存。那么带不同查询串的请求在 CDN 是不同对象,到了代理却合并成一个,回源时就可能拿到代理缓存的旧版本。这里的数字只用于说明比较方法:若两个查询串各请求一次,代理只回源一次,就足以暴露键不一致。
多个角色对同一事实理解不同时,不要靠口头对齐,把它拆成可核对的条目:
这样做的实际动作是:先只改一层的缓存键或 Vary,然后复测。如果 ETag 开始在各层一致,说明定位正确;如果仍不一致,说明还有一层键没对齐,下一步转向那一层,而不是继续清缓存。清缓存只能暂时掩盖,不能证明键已一致。
上面的“先查缓存键”并非万能。反例:如果源站对同一 URL 在短时间内返回了不同内容(例如发布过程中新旧版本并存,或按后端节点返回不同数据),那么即使各层缓存键完全一致,也会出现不同版本。此时 ETag 会随源站响应变化,而 Age 可能都很小。区分方法:直接绕过所有缓存请求源站多次,若源站本身就不稳定,问题不在缓存层,而在发布或数据一致性。这个反例说明,缓存键一致只能排除缓存层,不能排除源站。
先固定一个可复现的请求,逐层记录响应头,判断分歧出现在键、新鲜度还是源站。确认是键不一致后,只改一层并复测,用 ETag 是否收敛作为判断依据。若源站多次请求就不一致,转向排查发布流程或后端数据,而不是继续在缓存层调整。这样每一步的结果都能决定下一步往哪查,避免在多层之间反复试错。