网站缓存:一次修复引发另一类异常时怎样拆开依赖链

📍 WDQWDWQD987AAAAA:216.73.217.12
📱 Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)
🔗 /909a78c9f30f.html
📄

网站缓存:一次修复引发另一类异常时怎样拆开依赖链

先给结论:当你修好缓存的一层,却让另一层出现新异常,通常不是“修坏了”,而是这层修复改变了上游或下游对旧状态的依赖。最小可执行动作是:只改一个开关,记录改动前后同一批URL在浏览器、CDN或反向代理、应用三层各自返回的响应头与正文摘要,再根据差异判断依赖方向。缺少完整日志或权限时,你仍能做这个动作,但只能得出“哪一层先变”,不能直接断定最终用户看到的一定是什么。

矛盾现象的两种解释

假设一个常见场景:页面长期由CDN返回缓存副本,应用侧更新了模板,于是你清掉CDN缓存让新页面生效。结果页面变新了,但登录态、购物车数量或个性化推荐出现错乱。此时有两个成立条件不同的解释。

两种解释的区别不在于“缓存该不该清”,而在于异常随请求量变化,还是随数据结构变化。这个区别决定了下一步是回滚缓存策略,还是修下游解析逻辑。

能区分两种解释的证据

不要只看一个指标。回源量上升既可能是解释B,也可能是解释A导致下游反复重试。抓取量或请求量归零也不能单独证明处理正确,它可能只是监控采样丢失、机器人被临时拦截,或上游把请求合并了。

可区分证据按优先级排列:

  1. 同一URL在改动前后的响应头对比。记录Age、Cache-Control、ETag、Vary的变化。如果只有Age归零而正文结构未变,偏向解释B;如果正文结构或字段名变了,偏向解释A。
  2. 异常是否与登录态或Cookie相关。未登录用户正常、登录用户异常,通常说明Vary或缓存键没有把身份维度算进去,属于解释A。
  3. 把请求量压到极低再观察。在低流量窗口重复同一操作。异常消失,支持解释B;异常依旧,支持解释A。
  4. 回滚单层而非全部。只恢复CDN缓存策略,不动应用代码。若异常消失,说明问题出在缓存层与下游的契约上。

缺少权限时的最小动作

没有CDN后台、没有应用日志、没有APM,你仍然可以执行一个动作:用浏览器开发者工具或命令行请求同一组URL,分别带上和不带登录Cookie,记录状态码、响应头中的缓存字段和正文中关键字段的值。这个动作的结果会告诉你异常是否与身份维度绑定。

但必须说明不能推出的结论:你无法据此判断CDN节点是否全部刷新,也无法判断其他地区用户看到的内容。单点观测只能证明“这条路径上发生了什么”,不能代表全量。若需要全量结论,必须拿到CDN或反向代理的访问日志,或至少拿到多个地理位置的探测结果。

拆依赖链的顺序

把链路写成“浏览器缓存 → CDN或反向代理 → 应用 → 数据源”,然后按以下顺序验证,每步只改一个变量:

每一步的结果决定下一步:如果浏览器侧仍有强缓存,先处理响应头,否则后续观测会被用户端旧副本污染;如果缓存键不包含身份维度,先修缓存键,否则回源压力会持续存在。

一个注明假设的短例子

假设某站点把首页缓存有效期从一天改为十分钟,目的是减少陈旧内容。改动后,首页正常,但站内搜索接口开始返回空结果。假设应用把搜索索引也放在同一缓存层,且索引构建任务依赖缓存里的旧键名。十分钟过期后,旧键被清除,索引任务找不到键,返回空。这个假设下,修复动作没有错,错在索引任务把缓存当成了持久存储。

验证方法:把缓存有效期改回一天,观察搜索是否恢复。若恢复,说明依赖方向是索引任务依赖缓存键;若未恢复,说明还有另一个变量被同时改动。这个例子的数字只用于说明比较方法,不代表任何真实站点的表现。

拆依赖链的关键不是找到“谁错了”,而是找到“谁先变、谁后变”。先变的通常是修复动作直接作用的那一层,后变的是依赖它的那一层。把这两层的变更时间对齐,你就能决定是回滚、修契约,还是补一个兼容层。

图1 图2

nginx