缓存击穿是指热点key过期瞬间大量请求穿透缓存直打数据库,典型表现为Redis GET QPS平稳但DB慢SQL突增且集中于同一ID;解决方案包括加互斥锁、永不过期+逻辑过期、预热及避免轮询等待。

会,而且变慢的请求往往卡在数据库层,不是 Redis 响应慢,而是 DB 查询耗时突增——这是缓存击穿最典型的信号。
Redis GET QPS 正常但 DB 慢 SQL 突增
缓存击穿发生时,GET 命令的 QPS 通常没明显下降,甚至可能小幅上升(因为大量请求反复查缓存失败),但数据库侧会出现一批同 pattern 的慢 SQL,比如 SELECT * FROM user WHERE id = 10086 在几秒内被执行上百次。关键判断点是:这些慢 SQL 都指向同一个业务 ID,且对应 Redis 中刚过期的 key(如 user:10086)。
- 用监控工具(如 Prometheus + Grafana)对比
redis_cmd_total{cmd="get"}和mysql_slow_query_count的时间轴,看是否出现“GET 平稳、DB 耗时尖峰”同步现象 - 查 Redis 日志或
SLOWLOG,确认该 key 的EXPIRE或DEL操作是否集中在某秒发生 - 检查该 key 的 TTL 设置:如果业务上它是长期有效的热点数据(如配置项、用户基础信息),却用了固定
EX,就极易触发
别用 SETNX 轮询锁卡住所有请求
常见错误是:缓存未命中时,用 SETNX 抢锁,失败者循环 SLEEP 等待,直到拿到锁或超时。这会让大量请求堆积在 Redis 层,延迟从 DB 转移到锁竞争,前端看到的是“请求卡住”,而不是“响应慢”。
-
SET key value EX 5 NX的EX必须设(防止锁永久残留),且值不宜过大(建议 ≤ 5 秒),否则后台任务失败后占位符长期存在 - 抢锁失败后,不要轮询等待,而是直接走 DB 查询并返回结果(不写缓存),或 fallback 到本地内存缓存的旧值(需业务接受短暂 stale)
- 异步更新任务(如投递到线程池或 Kafka)完成后再用
SET写入真实数据,并显式DEL占位符,避免残留
逻辑过期方案里 expire_time 字段容易漏校验
采用“缓存永不过期 + 数据体带逻辑过期时间”方案时,读取缓存后必须先解析 JSON,再比对嵌入的 expire_time 字段与当前时间。漏掉这步,就等于没做防护。
- 缓存值结构示例:
{"data": {"name": "Alice"}, "expire_time": 1720845000000} - 代码中不能只判断
redis.get(key) != null,必须反序列化后取expire_time做System.currentTimeMillis() > expire_time - 异步刷新任务要保证幂等:即使多个请求同时触发,最终只应有一次 DB 查询和一次
SET写入
真正难处理的不是加锁或改结构,而是识别“哪个 key 正在击穿”——它往往藏在慢 SQL 的 WHERE 条件里,而不是 Redis 监控的异常指标中。盯着 DB 日志里重复出现的单个 ID,比盯着 Redis 的命中率更有用。


















