缓存穿透是指请求的key在缓存和数据库中均不存在,导致每次请求都绕过缓存直接访问数据库;其核心是数据本身不存在,而非缓存失效或配置错误,典型场景包括恶意攻击、非法id扫描等。

这不是“绕过缓存后拿到旧数据”,而是客户端或中间层误用了本地缓存,和 Redis 或服务端缓存无关。真正的缓存绕过(比如直连数据库)不会返回“旧数据”,只会返回最新数据或报错;而你看到的“绕过缓存却拿到旧值”,大概率是请求根本没发到服务端,或在半路被某层缓存截胡了。
一、先确认是不是真绕过了服务端缓存
很多所谓“绕过”其实是假象。用以下方式快速验证:
- 在接口入口(如 Spring Boot 的 Controller 方法开头)加一行日志:log.info("request arrived, key={}", key);,同时记录当前时间戳和缓存读取动作;
- 用 curl 或 Postman 直接调用后端地址(绕开 Nginx、CDN、浏览器),观察日志是否触发、返回内容是否最新;
- 对比响应头:Cache-Control、ETag、Last-Modified 是否存在,特别是 max-age=3600 或 public 这类字段——说明前端或代理在自作主张缓存;
- 检查是否启用了 HTTP 304 Not Modified:浏览器收到 304 就直接复用本地旧响应,根本不会走你写的业务逻辑。
二、重点排查四类“隐身缓存层”
真正导致“绕过服务端却拿旧数据”的,往往是下面这几层在偷偷生效:
- 浏览器强制缓存:前端代码设置了 response.setHeader("Cache-Control", "public, max-age=3600"),且没配 Vary: Authorization,导致登录态不同但共用同一份缓存;
- Nginx 代理缓存:配置了 proxy_cache,但 key 没包含 query 参数或 header(如 token),多个用户请求被映射到同一个缓存 entry;
- CDN 缓存:CDN 把 /api/goods/123 缓存了,但商品更新后没主动 purge,也没配好 cache key 规则(比如忽略 ?v=20260915 这类版本参数);
- 移动端本地缓存:App 内 WebView 或 OkHttp 设置了默认缓存策略,对 200 响应自动缓存 5 分钟,且未校验 ETag。
三、快速定位哪一层在作祟
按顺序排除,效率最高:
- 用 Chrome 无痕窗口 + 禁用缓存(DevTools → Network → ✅ Disable cache)重试,如果此时数据变新,问题就在浏览器;
- curl -I http://your-api.com/xxx,看响应头有没有 X-Cache: HIT from nginx 或 X-Cache: HIT from cloudflare;
- 在 Nginx access_log 里加 $upstream_cache_status 字段,查日志中是否大量出现 HIT;
- 服务端打点时,额外打印 request.getRemoteAddr() 和 request.getHeader("User-Agent"),看是不是某个 App 或爬虫 IP 固定返回旧值。
四、修复建议:让缓存“可知、可控、可破”
- 所有对外 API 响应头统一加:Cache-Control: no-store(禁止任何中间层缓存)或 no-cache(强制校验);
- 若必须用 CDN/Nginx 缓存,cache key 必须包含关键变量,例如:proxy_cache_key "$scheme$request_method$host$request_uri$http_authorization";;
- 敏感数据接口(如用户信息、订单详情)禁用 GET 缓存,改用 POST + body 传参,或加随机 query 参数(如 ?_t=1726454359);
- 前端发请求时显式设置:headers: { 'Cache-Control': 'no-cache' },并确保 axios/fetch 不启用默认缓存。











