Redis分布式锁用Lua脚本保证解锁原子性,核心是将“校验锁归属”和“删除锁”合并为一个原子操作:通过EVAL执行脚本,先GET比对ARGV[1](加锁时的唯一ID),相等才DEL,否则返回0,避免A超时后误删B的锁。

Redis 分布式锁用 Lua 脚本保证解锁原子性,核心就一点:把「校验锁归属」和「删除锁」两个动作塞进一个脚本里,让 Redis 一次性执行完,中间不插队。
为什么不能直接 DEL?
直接调用 DEL 命令释放锁,看似简单,实则危险。比如客户端 A 拿到锁后业务执行超时,锁自动过期被释放;此时客户端 B 成功加锁;A 恢复后仍执行 DEL —— 结果删掉了 B 的锁。问题根源在于:判断“这锁是不是我的”和“删不删”,是两次独立的网络请求,中间可能穿插其他客户端的操作。
Lua 脚本怎么做到原子执行?
Redis 执行 Lua 脚本是原子的:整个脚本从开始到结束,不会被其他命令打断。所以只要把逻辑写清楚,就能确保安全:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 用 redis.call("GET", KEYS[1]) 取出当前锁的 value
- 跟传入的 ARGV[1](即加锁时写的 client_id)严格比对
- 相等才执行 redis.call("DEL", KEYS[1]),否则返回 0
典型脚本长这样:
if redis.call("GET", KEYS[1]) == ARGV[1] thenreturn redis.call("DEL", KEYS[1])
else
return 0
end
调用时的关键细节
脚本写对只是第一步,调用方式也得规范:
- 必须用 EVAL 或 EVALSHA 执行,不能拆成 GET + DEL 两步走
- KEYS[1] 传锁的 key 名,ARGV[1] 传加锁时设置的唯一标识(如 UUID)
- 返回值是 Long 类型的 1 或 0,要显式判断,不能只看是否抛异常
- 如果锁已过期,GET 返回 nil,跟 ARGV[1] 比较结果为 false,脚本自然返回 0,安全
常见误区提醒
有些做法看着省事,实际埋雷:
- 不用 GET,改用 EXISTS 或其他间接方式判断——无法比对 value,失去身份校验意义
- 在脚本里用 pcall 包裹 DEL 来“防错”——掩盖问题,反而让失败变得不可见
- 把重入计数逻辑(如 UUID:count)硬塞进这个脚本——增加复杂度,易出错;更推荐业务层控制或换 Redisson

















