前端知库

CDN 和反向代理缓存异常应该怎样排查?

从浏览器、CDN、反向代理到源站拆解缓存链,说明缓存键、Vary、回源、Age 和用户数据隔离,并给出按证据定位旧内容与错误响应的方法。

参考答案

简答

  • 先画出响应经过的层:浏览器缓存、Service Worker、CDN、反向代理、应用,再逐层找证据。
  • 缓存键必须包含所有影响结果的维度,否则会串语言、串用户。
  • 源站没有日志不等于用户没拿到响应,可能在边缘命中;错误响应也可能被缓存。
  • HTML 与带哈希资源分开设置策略,发布时先上传新资源再切换入口。

用户反馈“我已经发布新版本,但有人仍看到旧页面”,也可能出现“中文用户拿到英文”“登录后看到别人的内容”或“源站返回 200,浏览器却解析成错误页”。这些现象经常被统称为 CDN 缓存问题,但真正的责任层可能是浏览器、Service Worker、CDN、反向代理、源站应用或发布流程。

先把一次响应经过的层画出来

一个典型请求可能经过:浏览器内存/HTTP 缓存 → Service Worker → CDN 边缘节点 → 反向代理 → 应用服务器 → 数据库。每一层都可能缓存、改写、压缩或生成响应,且看到的状态字段不一定来自同一层。

先记录完整请求和响应:URL、方法、请求头、响应状态、Age、Date、ETag、Cache-Control、Vary、Via、CDN 自定义命中头、Content-Type 和响应体摘要。只截图页面文字,无法知道是哪一层提供了内容。

缓存键必须包含所有影响结果的维度

如果源站根据语言、编码、设备、授权状态或实验分组返回不同表示,缓存键必须体现这些条件。Vary 声明了影响选择的请求头,但 CDN 还可能有自己的缓存键、规范化和忽略规则,不能只看源站配置就假定边缘节点完全照做。

http
Vary: Accept-Encoding, Accept-Language
Cache-Control: public, max-age=60

用户身份相关内容通常不适合共享缓存。把 Cookie 加进 Vary 可能导致缓存碎片化,也不能替代 private、no-store 和权限设计;如果缓存平台没有正确隔离 Cookie 或授权头,风险就是数据泄露,不只是命中率下降。

查询参数也可能参与缓存键,但平台可能忽略追踪参数、排序参数或部分参数。应用如果把 ?user=... 当成权限边界,却没有确认 CDN 的实际键规则,就可能把错误响应共享出去。敏感权限必须在源站每次验证,不能靠 URL 难猜或前端隐藏。

命中、未命中和回源不是同一层的状态

CDN 命中可能直接返回边缘保存的响应,未命中则回源获取并可能写入边缘缓存。源站日志没有请求,不等于用户没有拿到响应;它可能在边缘命中。反过来,源站日志出现请求,也不表示浏览器一定拿到源站刚生成的正文,因为中间层可以改写、压缩或缓存错误。

Age 可以帮助判断共享缓存中的响应年龄,但字段缺失不能直接证明没有缓存;浏览器缓存、Service Worker 和私有代理有自己的可见性。X-Cache、CF-Cache-Status 等头是平台约定,不是所有 CDN 通用标准,排查脚本不能只依赖一个厂商字段。

HTML 和带哈希资源应使用不同发布策略

入口 HTML 需要较快发现新资源,通常采用短新鲜期或验证策略;带内容哈希的 JS、CSS 和图片可以长时间缓存。发布时必须先上传新资源,再让入口引用它们;删除旧资源时还要考虑已经打开的旧页面和 CDN 的旧入口。

如果部署后出现 JS 404,先检查入口 HTML 引用了哪个版本、该文件在 CDN 和源站是否存在、CDN 是否仍缓存旧入口,以及 Service Worker 是否返回旧清单。仅清理浏览器缓存可能暂时绕过问题,却没有修复发布顺序或缓存失效。

错误响应也可能被缓存

404、403、502 或应用返回的错误页面可能带有可缓存头,或者被平台按默认规则短暂保存。用户持续看到旧 404 时,不一定是静态文件真的不存在;用户看到旧 500 时,也不一定表示源站当前仍失败。

错误页应有明确缓存策略,并与正常资源区分。发布验证要检查错误响应的状态码、Content-Type 和缓存头,避免把一份应用入口 HTML 以 200 返回给不存在的静态资源请求,导致浏览器后续报告模块解析或 MIME 错误。

代理还可能改变请求和响应

反向代理可能终止 TLS、添加或删除响应头、压缩正文、重写路径、处理重定向或设置自己的缓存策略。源站看到的 Host、协议和客户端 IP 也可能由转发头表达,应用必须按受信代理拓扑解析,不能无条件信任任意 X-Forwarded-*。

代理配置错误还可能导致 Vary 丢失、Set-Cookie 被缓存、Range 请求被合并或 SSE 响应被缓冲。遇到问题要分别查看浏览器收到的响应、代理日志和源站收到的请求,不能只在源代码里搜索一个 Cache-Control 字符串。

一套可重复的排查顺序

先用带唯一查询参数或平台提供的调试头确认当前请求到达了哪一层,但不要把随机参数当永久修复,因为它可能绕过缓存键而增加回源压力。然后比较同一 URL 在不同地点、不同 Cookie、不同 Accept-Encoding 和不同语言头下的响应头与正文摘要。

接着检查:

  1. 浏览器是否命中 HTTP 缓存或 Service Worker;
  2. CDN 是否命中、回源还是返回旧对象;
  3. 缓存键是否包含语言、编码、查询参数和用户状态;
  4. 反向代理是否修改了路径、头、压缩或响应缓存;
  5. 源站实际生成了什么状态和正文;
  6. 发布版本是否同时上传入口和带哈希的资源,并在刷新 CDN 缓存后重新验证。

最终修复应让响应策略服务于资源语义:公共不可变资源使用版本化 URL,入口和个性化内容使用适当验证与隔离,错误和实时流避免被错误缓存。缓存命中率只是一个指标,不能用来交换用户数据隔离和发布正确性。