必须用分布式锁,因本地锁仅限单jvm进程,集群下各实例锁互不感知,无法阻止多节点并发回源;应使用set key value ex seconds nx原子命令或redission trylock实现跨进程互斥,并配合二次检查、空值缓存与lua安全删锁。

Redis缓存击穿必须用分布式锁,本地锁(synchronized)在集群环境下完全无效。
为什么单机锁在生产环境会失效
微服务或分库分表架构下,同一请求可能被负载均衡到不同机器。哪怕你用 synchronized 包住数据库查询逻辑,每个 JVM 实例都有一把独立的锁,根本拦不住并发请求。现象是:缓存刚过期,5 台应用服务器同时发现 redis.get("hot:product:1001") 为空,全部冲向数据库——击穿照旧发生。
- 本地锁只对当前 JVM 进程有效,无法跨进程、跨机器协调
- Redis 本身无状态,
SETNX是唯一能天然支持分布式互斥的原子操作 - Spring Cache 的
@Cacheable默认不带锁,直接启用会放大击穿风险
SETNX + 过期时间的正确写法
Redis 2.6.12 之后推荐用 SET key value EX seconds NX 一条命令完成「设置+过期+仅当不存在」三件事。老版本必须拆成 SETNX + EXPIRE,但存在竞态漏洞:如果 SETNX 成功而 EXPIRE 失败,就会留下一个永不过期的死锁 key。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- ✅ 正确(Redis ≥ 2.6.12):
SET hot:product:1001 "data" EX 300 NX - ❌ 危险(老版本):
SETNX hot:product:1001 "lock"→EXPIRE hot:product:1001 300 - 锁 key 必须带业务标识,避免不同 key 共用同一把锁(如
mutex:hot:product:1001)
锁释放必须用 Lua 脚本防误删
不能简单用 DEL 删除锁:A 线程获取锁后执行超时,锁自动过期;B 线程拿到新锁并开始重建缓存;此时 A 线程终于执行完,一挥手 DEL 把 B 的锁也删了——后续所有请求又会并发穿透。
- 必须校验锁值(比如用 UUID),只有自己设的锁才能删
- 删除动作必须原子化,用 Lua 脚本:
EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 hot:product:1001 "a1b2c3" - 锁超时时间要明显大于 DB 查询耗时(例如 DB 平均 200ms,锁设 5s),否则容易出现「锁过期但任务未完成」
实际代码中容易漏掉的三个点
很多团队写了锁逻辑,线上还是击穿,问题往往出在边界处理上:
- 没做二次检查:线程拿到锁后,必须再查一次 Redis,确认缓存仍为空才查 DB(防止锁等待期间别人已写入)
- 没处理锁获取失败后的重试策略:直接返回 null?还是自旋等待?还是降级返回旧数据?需按业务容忍度定
- 没兜底清理机制:如果业务异常退出(如 OOM、kill -9),锁无法释放。建议加监控告警 + 定时扫描过期异常锁
最麻烦的不是写锁,而是确保锁的生命周期和业务逻辑严丝合缝——稍有松动,高并发下就露馅。










