互斥锁不能完全解决缓存击穿,根本原因是其可靠性依赖lockexpiry设置、原子性释放(lua校验)和收敛型重试策略;任一环节缺失(如锁过期、误删锁、固定休眠)都会导致并发窗口泄露,引发击穿。

Redis互斥锁不能完全解决高并发击穿,根本原因在于它把“一致性”和“可用性”强行绑定,而分布式环境下这两者天然冲突。 它能拦住大部分并发请求,但只要出现锁过期、进程崩溃、网络延迟或释放逻辑错误,就会漏出并发窗口——此时击穿照旧发生,甚至更隐蔽。
为什么锁会提前失效:lockExpiry 设置不当直接导致互斥失效
互斥锁的可靠性极度依赖 lockExpiry 参数。这个值不是拍脑袋定的,必须大于「最慢一次数据库查询 + 缓存写入」的耗时上界。现实中常见错误:
- 设成固定 30 秒,但某次 DB 查询因慢 SQL 或连接池打满耗时 42 秒 → 锁自动释放,第二个线程成功加锁,两个线程同时查库
- 用平均耗时(比如 15 秒)乘以 2 得到 30 秒,但没考虑 P99 尾部延迟(实际可能达 60 秒)
- 业务逻辑里嵌套了第三方 HTTP 调用,超时不可控,却没把这部分时间纳入
lockExpiry计算
一旦锁提前释放,SETNX 就不再互斥,后续请求会像没锁一样涌向数据库。
为什么释放锁可能误删:DEL 操作不带身份校验就是定时炸弹
很多实现直接用 DEL lock:{key} 释放锁,这是高危操作。典型崩塌链路:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 线程 A 加锁成功,value 是
"a1b2c3",但业务执行慢,锁在 30 秒后自动过期 - 线程 B 在锁过期瞬间抢到新锁,value 是
"d4e5f6" - 线程 A 此时终于执行完,调用
DEL lock:{key}—— 它删掉的是线程 B 的锁 - 线程 C 立刻加锁成功,和线程 B 同时读库 → 击穿重现
正确做法必须用 Lua 脚本原子校验再删:if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end。漏掉这一步,锁机制形同虚设。
为什么重试逻辑容易雪崩:休眠策略不收敛会放大压力
没拿到锁的线程通常会 sleep 后重试,但若所有线程都用固定间隔(比如统一 sleep 100ms),会形成“惊群效应”——醒来瞬间集体查缓存、集体抢锁,反而加剧 Redis 和数据库负载。
- 固定 sleep:N 个线程在 t+100ms 同时发起
GET+SETNX,Redis QPS 突增 - 无退避:连续失败 5 次仍用 100ms,不如改用指数退避(100ms → 200ms → 400ms…)
- 不检查缓存是否已就绪:sleep 醒来第一件事应该是
GET,而不是直接抢锁;很多代码跳过这步,徒增无效SETNX
真正健壮的重试,是“先查缓存,命中即走;未命中才按退避策略尝试加锁”,否则锁没抢到,缓存却已被别人写好,你还硬要抢,纯属浪费。
互斥锁的脆弱点不在设计,而在落地细节:lockExpiry 要覆盖全链路最坏延迟,DEL 必须绑定 value 校验,重试必须带退避且优先查缓存。少一个,就等于在击穿防线上凿了个洞——它不会彻底失效,但会在你最不想出问题的时候,悄悄漏过去。










