锁key被allkeys-lru/lfu淘汰是常见坑:因不区分TTL,冷锁被误删致并发失控;volatile策略同样淘汰带TTL的锁;唯一可靠解法是隔离锁到专用实例或DB并配noeviction。

锁 key 被 allkeys-lru / allkeys-lfu 淘汰了
这是最常踩的坑:你用 SET lock:order123 clientA NX PX 30000 加了锁,但 Redis 配置了 maxmemory-policy allkeys-lru,而这个锁 key 访问极少(只写不读),内存一满就被当成“冷数据”删掉了——结果锁提前消失,多个客户端同时进入临界区。
根本原因在于:allkeys-* 策略不区分是否设了 TTL,连锁这种必须长期存活的 key 也会淘汰;而 volatile-* 策略虽只针对带过期时间的 key,但锁本身就有 TTL,所以照样会被删。
- 别指望“设了 TTL 就安全”——
volatile-lru、volatile-ttl同样会淘汰你的锁 key -
noeviction虽能阻止淘汰,但写入失败会直接报错(error) OOM command not allowed when used memory > 'maxmemory',不适合锁场景 - 真正可行的是隔离:把锁 key 放进独立 Redis 实例,或至少用专用 DB(如
SELECT 15),并配maxmemory-policy noeviction
为什么不能靠定期删除/惰性删除保锁
Redis 的过期 key 清理(惰性 + 定期)只管“过期”,不管“被内存策略淘汰”。哪怕你给锁设了 30 秒 TTL,只要它在内存满前没被访问(惰性不触发)、也没被定期扫描抽中(概率低),就可能被 allkeys-lru 直接物理删除——此时 TTL lock:order123 返回 -2(key 不存在),不是 -1(未过期)。
换句话说:过期机制和淘汰机制是两套平行逻辑,互不感知。前者解决“该不该留”,后者解决“能不能留”。锁失效往往死在这第二关。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 定期删除默认每 100ms 抽 20 个带 TTL 的 key,锁 key 如果没被抽中,就永远等不到清理机会
- 惰性删除只在
GET/EXISTS等读操作时触发,而分布式锁几乎不读自己(除非主动查),等于“自闭式过期” - 所以依赖过期机制保锁,本质是赌概率,生产环境不可接受
Redisson 的 Watchdog 为什么也救不了被淘汰的锁
Redisson 的看门狗(Watchdog)机制本质是后台线程定时执行 PEXPIRE 续期。但它有个硬前提:锁 key 必须还存在。一旦 key 已被内存淘汰策略物理删除,PEXPIRE lock:order123 30000 就变成对一个不存在 key 的操作,直接静默失败,续期彻底失效。
这时候你看到的现象是:锁获取成功 → 业务执行中 → 某次续期后突然发现 lock:order123 不见了 → 其他客户端趁虚而入。
- Watchdog 默认每 10 秒续一次,但内存淘汰是即时发生的,没有“预警窗口”
- 即使你调大
lockWatchdogTimeout,也只是延长单次过期时间,不改变 key 被淘汰的风险 - 唯一可靠解法是让锁 key 完全避开淘汰路径:专用实例 +
noeviction,或至少确保锁所在 DB 内存余量充足(比如预留 30%)
生产环境锁 key 隔离的实操底线
别在缓存集群里混用锁,这是血泪教训。真实线上出问题的案例,90% 都是因为锁和商品缓存共用一个 Redis 实例,且开了 allkeys-lru。
- 最低成本方案:用
SELECT 15切换 DB,配置maxmemory-policy noeviction,并在应用层强制所有锁操作走该 DB - 更稳妥方案:单独部署一套轻量 Redis(哪怕单节点),专跑锁和 session,禁用所有淘汰策略
- 命名强约束:所有锁 key 必须带统一前缀(如
lock:),配合监控告警——当DB 15的used_memory超过 80%,立刻人工介入
淘汰策略不会跟你讲道理,它只认内存水位和算法。锁 key 的“存在性”必须由基础设施兜底,而不是靠业务逻辑去猜、去续、去赌。

















