先给结论:只有当你确认各层缓存拿到的“版本标识”可比时,差异才有定位价值。否则,你看到的很可能是同一页面的不同表示,而不是缓存不一致。这个结论在一种情况下会失效:页面本身按用户代理、语言或地域返回不同内容,此时各层缓存返回不同版本属于预期行为,应先固定请求条件再谈一致性问题。
多层缓存通常包括浏览器、CDN 边缘节点、反向代理和应用层缓存。它们返回不同内容,常见原因不是某一层坏了,而是每层收到的请求头不同。定位前先固定四个变量:请求 URL、请求方法、User-Agent、Accept-Language。缺少其中任何一个,比较结果都不可靠。
实际操作:用同一组请求头分别请求源站和 CDN 边缘地址,保存完整响应头。结果会影响下一步——如果源站和边缘的 ETag、Last-Modified 或 Cache-Control 不一致,问题在缓存同步;如果这些标识一致但正文不同,问题在内容生成或压缩环节。
判断“不同版本”不能只看正文肉眼差异。优先比较响应头中的版本标识:
ETag:强 ETag 不同通常意味着内容实体不同;弱 ETag 只表示语义等价,不能直接当作内容差异证据。Last-Modified:时间不同但正文相同,可能只是回源时间差,不构成不一致。Age:数值差异说明各层缓存命中时间不同,属于正常现象。Vary:如果缺失或与内容协商维度不匹配,不同层可能缓存了面向不同请求的响应。假设一个例子:源站返回 ETag: "abc",CDN 返回 ETag: "def",且正文差异只在页脚时间戳。此时先怀疑页脚时间戳是否参与了 ETag 计算。把时间戳从 ETag 生成逻辑中排除后,如果各层 ETag 恢复一致,说明原问题是标识生成策略,而不是缓存污染。
以下证据指向缓存不一致:同一请求头组合下,不同边缘节点返回不同正文;清除某一层缓存后差异消失;源站与边缘的 Cache-Control 不一致。
以下证据指向内容协商不一致:请求头中 Accept-Encoding 或 Accept-Language 不同;响应中 Vary 包含多个维度但缓存键未覆盖;移动端与桌面端返回不同 HTML。后者不是缓存故障,而是缓存键设计问题。
一个可区分的动作:对同一 URL 分别发送带 Accept-Encoding: gzip 和不带该头的请求。如果只有带压缩头的响应出现差异,问题在压缩层或 Vary 配置,不在缓存层本身。这个结果会决定你下一步是改缓存键,还是改压缩策略。
已经尝试清除 CDN、重启反向代理、刷新应用缓存仍未解决,通常遗漏的是缓存键中的某个条件。常见遗漏包括:
验证方法:构造两个仅在一个条件上不同的请求,观察返回版本是否分叉。如果分叉,把该条件加入缓存键或从内容生成逻辑中移除。动作结果直接影响下一步——缓存键修正后,各层应返回同一版本;若仍分叉,再检查源站是否在响应中动态注入时间戳或随机数。
上述方法适用于静态或半静态页面,且各层缓存均支持读取响应头。对于按用户登录状态返回完全个性化内容的页面,多层缓存版本不同是设计使然,不应追求全局一致,而应改为不缓存或按用户分片缓存。另外,站点地图和 robots.txt 的抓取限制不解决缓存版本问题;HTTPS 也不保证各层缓存内容一致。不同搜索引擎对缓存版本的处理方式须分别核查,不能用一个平台的表现推断另一个平台。
下一步动作:选定一个受影响 URL,固定请求头后依次请求浏览器缓存、CDN 边缘、反向代理和源站,记录每层的 ETag、Vary、Age 和正文哈希。哪一层开始出现哈希分叉,就优先检查该层前一跳的缓存键与内容协商配置,而不是继续清理所有缓存。