跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

Redis为何会释放别人的锁_用Lua脚本比对UUID防误删

根本原因是DEL操作不认人,必须用Lua脚本保证“校验value+删除key”原子执行:先通过redis.call('GET', KEYS[1])获取锁值,仅当等于ARGV[1](当前线程唯一标识)时才执行redis.call('DEL', KEYS[1]),否则返回0,彻底杜绝误删。 为什么 Redis 会释放别人的锁 根本原因就一条:
DEL
操作不认人。只要键存在,
DEL lock_key
就直接删,不管这个锁是谁设的、值是多少。常见误删场景是:线程 A 的业务执行超时,Redis 自动过期释放了锁;线程 B 立即抢到锁并写入自己的标识(比如
"uuid-b"
);线程 A 此时终于执行完,还拿着自己老的
"uuid-a"
去
GET
判断,发现键已不存在(或刚好被 B 写入),但它的逻辑没做兜底,直接执行
DEL
—— 实际上删的是 B 刚设的锁。 用 UUID 标识锁持有者但不用 Lua 的风险 只靠
SET lock_key "uuid-a" NX EX 30
+
GET lock_key
再
DEL
是不够的。问题出在“判断”和“删除”之间存在时间窗口: 线程 A
GET lock_key
返回
"uuid-a"
,确认是自己的锁 此时锁恰好过期,Redis 自动删除该 key 线程 B 成功
SET lock_key "uuid-b" NX EX 30
线程 A 执行
DEL lock_key
,删掉的是线程 B 的锁 这个窗口哪怕只有几毫秒,在高并发或 GC 暂停场景下也极易触发。UUID 只解决了“谁设的”问题,没解决“判断与删除必须一气呵成”这个原子性要求。 为什么必须用 Lua 脚本做比对删除 Redis 执行 Lua 脚本是原子的:整个脚本要么全执行,要么全不执行,中间不会被其他客户端命令插入。典型脚本如下: Redis 8.2.3 Redis 8.2.3 是一款安全优先的高性能键值存储系统。该版本紧急修复了可能引发远程代码执行(RCE)的高危漏洞(CVE-2025-62507),并解决了 HyperLogLog 及 Cuckoo Filter 等数据结构在特定场景下的崩溃问题。建议所有用户立即升级,以保障生产环境的系统稳定与数据安全。 下载
if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end
关键点:
KEYS[1]
是锁 key(如
"lock:order:123"
)
ARGV[1]
是当前线程的 UUID(如
"a1b2c3-...-d4e5"
)
redis.call('get', ...)
和
redis.call('del', ...)
在同一个原子上下文中完成 返回
1
表示删除成功,
0
表示不是自己的锁,不做任何操作 实际使用中容易忽略的细节 很多人写了 Lua 脚本却还是出问题,常踩的坑有: 没校验脚本执行返回值 ——
execute(...)
返回
0
时,仍当成删除成功继续后续逻辑 UUID 生成方式不合理 —— 用
UUID.randomUUID()
每次都新生成,导致重入失败;应在线程首次获取锁时生成并复用 锁 key 设计没带业务上下文 —— 比如统一用
"lock"
,多个业务互相干扰;应按资源粒度隔离,如
"lock:stock:1001"
过期时间设得太短 —— 业务耗时波动大时,锁提前释放,Lua 脚本再严谨也防不住竞争 最要命的一点:以为加了 UUID + Lua 就万事大吉,结果忘了锁本身没做可重入设计,同一线程重复获取锁时直接失败,反而引发更隐蔽的并发问题。

相关文章