404 not found的字面意思是服务器没有找到请求的资源,但在多层缓存架构里,你看到某个URL返回404,并不代表源站真的删了它。更常见的情况是:CDN、反向代理、应用层缓存各自保存了不同版本的响应,某一层拿着旧缓存或错误缓存对外输出,于是同一个URL在不同节点、不同时间返回200和404两种结果。定位这类一致性问题的核心动作是逐层剥离缓存,观察响应头的变化,而不是先去改源站配置。
源站返回404,说明请求确实到达了应用,且应用判定资源不存在。缓存返回404,说明请求根本没到源站,某一层直接把存下来的404吐了出来。这两种情况处理方式完全不同,所以第一步是确认响应来源。
可执行的判断依据是响应头。重点看这几类字段:
Age:大于0说明响应来自缓存,数值大致反映该副本已存在多久。X-Cache、CF-Cache-Status、X-Proxy-Cache等自定义头:不同厂商命名不同,但通常会标出HIT、MISS、EXPIRED、BYPASS等状态。Cache-Control、Expires、Vary:决定这一层是否该缓存、缓存多久、按什么维度区分副本。ETag、Last-Modified:用于判断不同层拿到的到底是不是同一版本。如果同一个URL在A节点返回带Age的404、在B节点返回无Age的200,基本可以判断是缓存副本不一致,而不是资源真的不存在。反过来,如果所有层都无缓存标记、请求都打到源站,却仍然404,那问题在源站路由或数据层,与缓存无关。
以下情境为假设,仅用于说明比较方法,不代表任何真实项目。
假设某站点结构是:浏览器 → CDN → Nginx反向代理 → 应用。运维反馈某个栏目页“时好时坏”,有时200有时404。此时不要急着清缓存,按下面顺序做:
ETag。Age,看Nginx是否缓存了与源站不同的版本。ETag或响应体摘要放在一起比较。假设结果是:应用层稳定200,Nginx层返回404且Age很大,CDN层跟随Nginx也返回404。那么可以推断,问题出在Nginx缓存了一份旧的404,而它没有按预期失效。下一步动作是检查Nginx的缓存键和过期策略,而不是去翻应用代码。这个动作的结果会直接决定后续排查方向:如果清掉Nginx缓存后恢复正常,说明是缓存失效机制问题;如果清完仍然404,说明问题其实在更上游或应用本身,之前的推断被推翻。
多层缓存返回不同版本,很多时候不是“缓存坏了”,而是每层用来区分副本的键不一样。常见的分歧点包括:
Accept-Encoding、User-Agent、Cookie纳入缓存键。某一层按压缩格式分开存,另一层不区分,就会互相覆盖。Vary头声明了区分维度,但下游缓存没有正确实现,导致本应分开的副本被合并。要判断是否属于这类问题,可以构造两个只在一个维度上不同的请求,观察状态码是否分叉。如果分叉,说明该维度参与了缓存键;如果不分叉,说明该维度被忽略了。这个比较不需要完整权限,只要能发出请求并读取响应头即可。
很多情况下你拿不到CDN后台、看不到Nginx配置、也没有源站日志。此时仍可执行的最小动作是:
Age、ETag和缓存命中头。?probe=1),观察状态码是否改变。若改变,说明原URL命中了某层缓存,而这个参数绕过了它。Cache-Control: no-cache请求头时的响应差异。这些动作能帮你把问题范围缩小到“某一层缓存”,但不能据此断定是哪一层、也不能断定清缓存就能解决。请求量或抓取量在某个时间点归零,同样不能单独证明处理正确,它也可能是流量本身波动、监控采样变化或上游限流造成的。要得出可靠结论,需要至少两个独立来源的证据互相印证,例如响应头标记与源站访问日志同时指向同一层。
定位一致性问题的终点,不是“把缓存清了”,而是给出一句可验证的判断,例如:“Nginx层缓存了一份Age超过其TTL的404副本,导致CDN跟随输出404;应用层始终返回200。”这句话包含三层信息:哪一层、什么状态、与哪一层不一致。有了它,即使后续交给开发或运维,对方也能直接验证,而不必从头复现。若只能给出“有时候404有时候200”,问题会在各团队之间来回传递,定位成本反而更高。
最后要提醒的是,缓存一致性问题往往与内容更新、发布流程、回源策略交织在一起。单次清缓存可能让现象暂时消失,但如果缓存键或失效规则没有对齐,同样的问题会在下一次发布时重现。判断是否真正解决,要看在内容更新后各层是否按预期同步失效,而不只是看当下某一次请求返回了什么。