Redis驱逐策略(如allkeys-lru)会导致分布式锁key被主动删除而非过期,造成锁丢失;应禁用allkeys类淘汰策略,改用noeviction或隔离锁实例,并坚持SET NX PX原子写入与唯一value。

Redis 因驱逐(eviction)导致分布式锁丢失,本质是锁 key 被 Redis 主动删掉——不是过期,也不是被 DEL,而是内存不足时被 LRU/LFU 等策略“踢出”。这种情况在高负载、小内存或配置不当的 Redis 实例上极易发生,且极难复现和定位。
为什么 allkeys-lru 会让锁突然消失
当 Redis 配置为 maxmemory-policy allkeys-lru(或 allkeys-lfu、allkeys-random)时,它会无差别地从所有 key 中淘汰,包括你精心设置过 TTL 的锁 key。即使 SET lock:order:123 "uuid-abc" NX PX 30000 成功执行,只要内存告急,这个 key 就可能在 5 秒后被驱逐,而 Redis 不会触发任何过期事件,客户端也收不到通知。
表现是:业务日志里没看到锁超时或释放记录,但并发请求却同时进入临界区;监控里 evicted_keys 指标突增,used_memory_rss 接近 maxmemory。
- 锁 key 和普通缓存 key 混在同一实例,且未做命名空间隔离
- 业务方误用
set或hset写入大量热数据,挤占内存 -
maxmemory设置过小,或未预留足够 buffer(如 AOF rewrite、复制缓冲区)
如何确认锁丢失是驱逐导致的
直接查 Redis 运行时指标比翻日志更可靠:
- 执行
INFO memory,重点看evicted_keys是否 > 0,且与锁异常时间点吻合 - 执行
INFO stats,检查total_commands_processed和instantaneous_ops_per_second是否突增(说明请求压垮了内存) - 用
MEMORY USAGE lock:order:123查单个锁 key 占用,若返回 -1,说明 key 已不存在——此时再查get lock:order:123返回 nil,不能直接断定是过期,必须结合evicted_keys
注意:TTL lock:order:123 返回 -2 表示 key 不存在,返回 -1 表示 key 存在但无过期时间——但如果你用了 PX,它就绝不该是 -1;若是 -2,就要立刻去查 evicted_keys。
必须禁用 allkeys 类淘汰策略
分布式锁 key 必须具备“不被主动驱逐”的语义,这是底线。正确做法只有两条:
- 把锁所在的 Redis 实例严格限定为
noeviction策略:一旦内存满,新写入失败(返回(error) OOM command not allowed when used memory > 'maxmemory'.),至少让问题暴露在加锁阶段,而不是运行中静默丢锁 - 或者,彻底分离用途:锁专用一个 Redis(哪怕单节点),缓存另起一套集群;锁实例的
maxmemory只需够存几千个锁 key 即可,根本不会触达淘汰阈值
绝对不要依赖“我只设了 PX,它就一定活够 30 秒”——在 allkeys-lru 下,这句话毫无意义。TTL 只约束过期,不管驱逐。
显式 TTL + 原子写入仍是基础要求
即使禁用了驱逐,仍要保证锁创建本身安全:
- 必须用
SET key value NX PX 30000,而非分步的SETNX+EXPIRE,否则存在竞态窗口 - value 必须是全局唯一标识(如
UUID.randomUUID().toString()),用于后续 Lua 脚本安全释放 - 避免给锁 key 手动调用
PERSIST或EXPIREAT,这会破坏 TTL 的可预测性
最危险的组合是:allkeys-lru + SETNX + 无 TTL —— 锁既可能被驱逐,又永不释放,变成隐形死锁。
驱逐导致的锁丢失,往往发生在压测后期或流量高峰尾声,此时内存水位反复横跳,个别锁 key 在毫秒级窗口内被踢出。它不像网络分区或进程崩溃那样有迹可循,排查时容易陷入“锁明明设了 30 秒,怎么 5 秒就没了”的思维陷阱——先看 evicted_keys,再查内存,最后动代码。


















