redis分布式锁防缓存击穿需配合“查库+回填缓存”原子化执行,因setnx或set nx px仅保证互斥不保证结果可见,必须通过双重检查(加锁前查、锁内再查)闭环,且锁释放前须确保缓存写成功,否则仍会触发重复抢锁。

Redis 分布式锁能防缓存击穿,但必须配合“查库+回填缓存”逻辑原子化执行,且锁本身不能替代缓存策略设计。
为什么 setnx 或 set NX PX 不能单独防击穿
单纯用 SET key value NX PX 30000 加锁,只保证“最多一个客户端在执行”,但不保证它执行完后缓存一定被写入、或写入后其他请求能立刻读到。常见断点包括:
- 加锁成功后,查库失败或超时,没写缓存就释放锁 → 后续请求仍会重复抢锁
- 锁内写缓存时网络抖动或 Redis 写失败,缓存未落库 → 下一波请求又触发锁竞争
- 锁释放后,缓存还没写入(比如异步线程写),其他请求查不到缓存又开始抢锁
本质是:锁只管“互斥”,不管“结果可见”。防击穿的关键不是“谁先跑”,而是“谁写完、别人马上能读到”。
必须用双重检查(Double-Check)模式闭环
锁只是第一道防线,真正闭环靠“缓存存在性二次确认”——加锁前查一次缓存,加锁后写完再查一次。典型流程:
- 请求进来,先
GET user:123;命中则直接返回 - 未命中,尝试
SET lock:user:123 1 NX PX 5000;失败则短暂等待后重试GET - 成功加锁后,再次
GET user:123(防止并发请求中已有其他线程写入) - 仍为空,则查 DB,写入
SET user:123 {...} EX 3600,再删锁
关键点:GET 要在锁内再做一次,否则高并发下仍可能多个线程同时进入查库分支。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
超时时间必须分层设置,不能只靠一个 TTL
防死锁和防击穿失效都依赖时间控制,但三处超时含义不同,不能混用:
-
SET ... PX 5000:锁本身过期时间,建议 3–5 秒,够查库+写缓存即可 -
SET user:123 ... EX 3600:缓存数据过期时间,按业务热度设(如热点商品可设 10 分钟) - 客户端重试等待:建议
usleep(10000)(10ms)起跳,总重试上限 200ms,避免雪崩式排队
如果锁过期太短(如 1s),查库慢的请求会被强制释放锁,导致其他请求重复抢锁;过长(如 30s)则阻塞严重,且掩盖真实故障。
Redisson 的 tryLock() 不是银弹,得配对使用
用 Redisson.getLock("user:123").tryLock(3, 30, TimeUnit.SECONDS) 看似省事,但它只解决“获取锁”环节。漏掉两个关键动作:
- 没自动做锁内二次
GET检查 → 仍可能多线程查库 - 没封装缓存写入后的原子性校验 → 写失败时不会重试或降级
实际要用,得手动补上:
RLock lock = redisson.getLock("lock:user:123");
if (lock.tryLock(3, 30, TimeUnit.SECONDS)) {
try {
String cached = redis.get("user:123"); // 锁内再查一次
if (cached != null) return cached;
String dbData = loadFromDB();
redis.set("user:123", dbData, "EX", "3600");
return dbData;
} finally {
lock.unlock();
}
}
真正容易被忽略的是:锁释放前,必须确保缓存已写成功;而写失败时,要么抛异常让上游重试,要么降级返回空,不能静默吞掉错误。










