缓存击穿时本地锁无效,因无法跨进程互斥;应使用redis原子命令set nx ex或redission trylock实现分布式锁,并配合空值缓存、key收敛、过期偏移等策略综合治理。

缓存击穿发生时,本地锁根本不起作用
当多个请求同时发现 key 在 Redis 中不存在(比如热点商品详情页),又恰好都查完 DB 后准备回写缓存,这时如果只用本地锁(如 synchronized 或 ReentrantLock),每个应用实例的锁互不感知——三台机器上各有一个线程在各自 JVM 里加了锁,结果还是写了三次 DB、刷了三次缓存。本地锁只对单进程有效,而缓存击穿是跨进程/跨节点的问题。
分布式锁必须满足“互斥 + 自动释放”两个硬条件
选分布式锁不是看它多酷,而是看它能不能真正拦住并发回源。常见方案中:
-
Redis SET key value EX seconds NX是最轻量且够用的选择,但要注意:必须用原子命令,不能拆成SETNX+EXPIRE(中间失败会导致死锁) - 使用
Redission时,别直接用lock()不带超时,得用tryLock(waitTime, leaseTime, TimeUnit),否则网络抖动可能让锁永远不释放 - ZooKeeper 方案虽然强一致,但引入额外组件、延迟高,在纯缓存场景里性价比偏低
更推荐“逻辑层兜底 + 简单分布式锁”组合
光靠锁治标不治本。实际线上更稳的做法是:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 先尝试用
SET key value EX 60 NX获取锁;获取失败就主动 sleep 10–50ms 后重试(避免自旋打满 CPU) - 成功拿到锁的线程去查 DB,写入缓存后立刻
DEL锁 key(Redission 会自动续期,但手动删更可控) - 关键补充:DB 查询结果为 null 时,也写一个空对象(如
"null")+ 短 TTL(如 2 分钟),防止穿透;注意要和业务层约定好怎么识别空值 - 如果业务允许轻微不一致,甚至可以用“异步回写”:查 DB 后先返回,再发消息由消费者写缓存,彻底避开锁竞争
锁粒度和 key 设计不当,比选哪种锁更容易翻车
见过太多人把锁套在 getProductDetail(long id) 外面,却没意识到这个 id 来自前端 URL 参数——攻击者批量刷 /product/1 到 /product/100000,就会触发 10 万个互不相关的锁,Redis 内存暴涨、QPS 断崖下跌。正确做法是:
- 锁的 key 必须收敛,比如固定为
lock:product:{id},而不是拼出 10 万个不同 key - 对高频但低敏感的查询(如用户头像),干脆放弃锁,改用“过期时间随机偏移”:
EX 3600 + random(0, 600),让失效分散开 - 如果用了 Redission,确认
lockWatchdogTimeout配置合理(默认 30s),太短会误释放,太长会拖慢故障恢复
锁本身只是工具,真正决定效果的是你控制并发的边界是否清晰、key 是否可预测、空值是否被忽略——这些地方错一点,换什么锁都没用。










