缓存击穿表现为redis get qps稳定但db查询耗时突增、出现同业务id的慢sql;识别关键是ttl归零+高并发+单key热点。

缓存击穿时 GET 请求卡在 DB 上怎么识别
Redis 中某个热点 key 过期瞬间,大量并发请求同时穿透到后端数据库,导致 DB 压力陡增、响应变慢——这不是缓存雪崩(大面积 key 失效),也不是缓存穿透(查不存在的 key),而是典型的「缓存击穿」。最直接的表现是:监控里 Redis 的 get 命令 QPS 没明显跌,但应用层 DB query 耗时突增,且日志中出现一批相似的慢 SQL,对应同一个业务 ID(比如 user:10086)。
关键判断点:TTL 刚好归零 + 高并发读 + 单 key 热点。如果该 key 本应长期存在(如配置、用户基础信息),却设置了固定过期时间,就极易触发。
用 SETNX + 后台任务防穿透,但别锁整个请求链路
常见错误是:发现缓存缺失,立刻用 SETNX 抢锁,成功者查 DB 写缓存,失败者轮询等待——这会让所有请求卡在 Redis 层,延迟从 DB 转移到锁竞争,反而更糟。
正确做法是「只让一个请求去加载,其余请求直接回源或返回旧值」,具体可分两步:
- 用
SET key value EX 60 NX尝试设占位符(如"loading"),成功则异步触发更新任务(例如发消息到 Kafka 或投递到线程池) - 失败则不做等待,直接读 DB 返回结果(不写缓存),或返回本地内存中已有的旧缓存(需业务容忍短暂 stale)
- 异步任务完成后再用
SET写入真实数据,并删掉占位符(避免占位符长期残留)
注意:NX 是原子性保障,EX 必须设(防止锁永久残留),且占位符本身也应设较短 TTL(如 5 秒),避免后台任务失败后死锁。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
Redisson 的 RLock 在缓存加载场景下容易误用
有人直接用 RLock.tryLock(3, 10, TimeUnit.SECONDS) 包裹 DB 查询逻辑,以为能控制并发。问题在于:tryLock 的 waitTime 和 leaseTime 很难合理设置——waitTime 太短,大量请求走 DB;太长,前端超时;leaseTime 若小于 DB 查询耗时,锁自动释放,仍可能引发重复加载。
更稳妥的方式是结合信号量与状态标记:
- 先用
redis.set("lock:user:10086", "1", "NX", "EX", 5)尝试获取轻量锁 - 获取成功 → 提交异步任务并立即返回(不阻塞)
- 获取失败 → 查 DB,同时检查是否存在
user:10086:loading这类状态 key,有则说明正在刷新,可跳过写缓存
Redisson 的优势在分布式协调,不是缓存加载控制;硬套 RLock 反而增加复杂度和故障面。
异步更新后,如何避免缓存与 DB 短暂不一致被放大
异步更新天然带来延迟,若此时 DB 又被修改(比如用户资料更新),而缓存还没刷上来,就会出现「新 DB + 旧缓存」的错乱。这不是最终一致性问题,而是中间态被下游高频读取放大的问题。
- 对强一致性要求高的字段(如账户余额),不要走异步刷新,改用「更新 DB 后同步
DEL缓存」+ 「下次读自动重建」 - 对弱一致性可接受的字段(如用户昵称、头像),可在异步任务中加版本号或时间戳校验:查 DB 时带上上次缓存写入时间,若 DB 更新时间早于该时间,直接丢弃本次结果
- 所有异步任务必须有重试机制(如指数退避),失败时记录告警,不能静默丢弃
最容易被忽略的是:异步任务里的 DB 查询没加 SELECT ... FOR UPDATE 或没做幂等判断,导致多次刷新写入不同版本的数据。缓存更新不是“越快越好”,而是“越稳越准”。










