缓存击穿本质是热点key集体过期叠加高并发,需用带超时的setIfAbsent加锁、逻辑过期设计及动态预热机制协同防御。

缓存击穿不是“没缓存”,而是“刚好集体过期”
Redis里一个热点key(比如首页商品列表)设了10分钟TTL,结果第10分01秒那批请求全撞上缓存空,数据库瞬间被打满——这不是缓存没建好,是过期策略和并发控制没对齐。本质是时间窗口+高并发的双重挤压,不是靠加大Redis内存能解决的。
setIfAbsent加锁必须带超时,否则一卡全卡死
用SETNX或setIfAbsent抢锁时,不设EX参数就是埋雷。一旦线程崩在查库或网络抖动里,锁永远不释放,后续所有请求都在死等。
- 正确写法:
setIfAbsent("lock:hot_user_123", "1", Duration.ofSeconds(10)) - 锁超时时间建议 ≤ 缓存重建耗时 × 2,比如查库平均200ms,设5秒足够
- 别用
Thread.sleep(100)循环重试,QPS高时会把线程池拖垮;改用指数退避或直接返回旧数据
逻辑过期比物理过期更抗打,但得改value结构
不依赖Redis的TTL机制,而是把过期时间塞进value里(比如JSON字段expireAt),这样key永不淘汰,只由业务逻辑判断是否该更新——既防击穿,又保响应速度。
- value示例:
{"data": {"id": 123, "name": "小龙虾套餐"}, "expireAt": 1741724520} - 读取时先解包,检查
expireAt是否已过;若过,异步触发updateCacheAsync("hot_user_123"),当前请求仍返回旧值 - 更新缓存前务必二次校验:防止A线程刚写完,B线程又覆盖成更旧的数据
预热不能只靠启动加载,得盯住“动态热点”
系统启动时批量GET几百个已知ID,只能防住静态榜单;真正要命的是突发新闻、OpenClaw智能体触发的实时热搜——这类key根本不在预热清单里。
- 上线后用
SLOWLOG或Redis监控看TOP 20访问key,每5分钟扫一次,自动加入预热队列 - 对命中率突增>300%的key(比如
incr hot_key:xxx:hit),立刻触发GET + SETEX刷新 - 别让预热任务和主业务共用连接池,否则高峰期预热反而抢走正常请求资源

















