lua脚本能通过原子执行避免“校验+删除”竞态导致的误删锁,但无法解决锁过期设置不当、主从异步、集群跨slot、客户端失联及业务幂等性等问题。

不能保证绝对安全,但能消除“校验+删除”竞态导致的误删锁问题——这是生产环境中最常踩、后果最严重的坑。
为什么 Lua 脚本能解决误删锁,但不是万能的
Lua 脚本在 Redis 中是原子执行的,整个脚本从开始到结束不会被其他命令打断。这就堵死了“先 GET 再 DEL”中间被插队的漏洞。
常见错误写法(非原子):
GET lock:order # ⚠️ 此刻锁可能已被别人重设 DEL lock:order
对应的安全 Lua 脚本(原子):
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
- 脚本里
redis.call('get', ...)和redis.call('del', ...)是一个不可分割的整体 - 只要加锁时存的
value是唯一标识(如UUID + threadId),这个脚本就能确保“只删自己的锁” - 但它不解决:锁过期时间设置不合理、业务超时未续期、Redis 主从异步导致的锁丢失等问题
你必须自己控制的三个关键参数
再好的 Lua 脚本,参数配错一样翻车:
-
expireSeconds:不能拍脑袋定 30 秒。要基于业务最大耗时 + 网络抖动余量,建议用监控数据反推(比如 P999 耗时 + 200ms) -
requestId:必须全局唯一且不可预测。用UUID.randomUUID().toString() + "-" + Thread.currentThread().getId()比单纯用线程 ID 更可靠 -
EVAL或EVALSHA的调用方式:频繁用EVAL传完整脚本会增加网络和解析开销;生产环境推荐先SCRIPT LOAD缓存,再用EVALSHA执行
Lua 安全的边界在哪?这些情况它管不了
Lua 脚本只管“Redis 服务端这一层”的原子性,它不感知客户端状态:
- 客户端 A 加锁成功,但网络断开,没收到响应 → 实际已持锁,但代码认为失败 → 后续不会解锁,只能靠 TTL 释放
- Redis 集群模式下,
KEYS[1]被哈希到不同节点,而 Lua 脚本无法跨 slot 执行 → 直接报CROSSSLOT错误 - 主从切换瞬间,客户端 A 在旧 master 上加锁,新 master 还没同步该 key → 客户端 B 在新 master 上也能加同名锁
- 锁被正确释放后,没有做业务幂等校验 → 并发请求仍可能重复落库(Lua 不解决业务逻辑层问题)
真正容易被忽略的点是:Lua 脚本只保住了“释放锁”这一步的正确性,但整个分布式锁的可靠性,取决于你如何设计超时策略、如何应对网络分区、是否做了锁自动续期,以及——最关键的是,你有没有在业务层补上幂等和最终一致性兜底。










