安全释放锁是什么
释放分布式锁必须先确认「锁还是自己的」再删除,而且这两步要原子完成。否则会出现经典事故:业务 A 持锁期间任务超时,锁自动过期被业务 B 拿到,A 处理完后直接 DEL,把 B 的锁删掉。Lua 脚本把 GET 比对与 DEL 合成一次服务端执行,天然原子,是标准解法。
基本信息
| 适用版本 | EVAL 需 Redis 2.6+,全版本通用 |
|---|---|
| 锁值内容 | 客户端唯一 token,如 UUID 加线程号 |
| 调用形式 | EVAL script 1 key token,KEYS 与 ARGV 分离传参 |
| 返回值 | 1=删除成功(确属自己);0=已不是自己的锁 |
| 常见实现 | Redisson 等客户端库内置等价 Lua |
典型使用场景
- 订单防重复提交:多实例部署时保证同一订单只被处理一次
- 定时任务互斥:多台机器上的同一定时任务只允许一轮执行
- 库存扣减保护:配合原子脚本在锁内完成校验与扣减
- 资源初始化互斥:保证缓存预热、索引构建只跑一次
- 自研锁接替客户端库:把加锁与解锁都收敛到同一套 token 逻辑
- 故障复盘:确认历史事故是否源于误删他人锁
Lua 安全释放与调用
Lua 安全释放与调用
# 加锁:值必须是本次调用独有的 token
SET lock:order:42 6f1d2c4e-9a1b NX PX 5000
# 释放:只有值等于自己的 token 才删除
EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end" 1 lock:order:42 6f1d2c4e-9a1b
# 返回 1 = 删除成功(锁确实是自己的)
# 返回 0 = 锁已过期或已被他人持有,不做任何动作
# 续期:同样要校验 token,避免给别人的锁续命
EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('PEXPIRE', KEYS[1], ARGV[2]) else return 0 end" 1 lock:order:42 6f1d2c4e-9a1b 5000返回值读法
| 返回值 | 含义 | 后续动作 |
|---|---|---|
| 1 | token 匹配并删除成功 | 正常释放,业务可继续 |
| 0 | 锁已不属于自己(过期或他人持有) | 记录告警,排查任务是否超时 |
| nil | 脚本执行异常或语法错误 | 检查脚本引号转义与参数个数 |
客户端库(Redisson 等)都用等价 Lua 实现解锁;自研锁照抄这段即可,KEYS 与 ARGV 要通过参数传,不要拼进脚本文本。
安全释放只解决误删,不解决锁本身在主从切换瞬间可能被两个客户端同时持有的问题;对互斥性要求极高的场景应选用带共识的组件,并接受其性能与复杂度成本。