ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

【Redis进阶篇|第十一篇】释放锁时为啥也需要lua脚本

【Redis进阶篇|第十一篇】释放锁时为啥也需要lua脚本

1. 引言

在分布式锁的实现中,很多人会关注加锁时为什么使用 Lua 脚本,却容易忽略释放锁时同样需要 Lua 脚本控制。本文将从原子性、误删问题和并发安全三个角度,讲清楚释放锁时使用 Lua 脚本的必要性。

2. 释放锁的常见误区

一个常见的错误实现是:先获取锁对应的 value,判断是否等于当前线程的标识,如果相等则删除锁。这种「先判断、再删除」的两步操作在单线程下没有问题,但在并发环境下存在严重的竞态条件。

// 错误示例:非原子操作 String value = redisTemplate.get(lockKey); if (value != null && value.equals(threadId)) { redisTemplate.delete(lockKey); }

这段代码的问题在于:判断和删除是两个独立的 Redis 命令,中间可能被其他线程插入执行,导致锁被误删。

3. 为什么需要 Lua 脚本

释放锁时使用 Lua 脚本的核心原因是为了保证原子性。Redis 在执行 Lua 脚本时是单线程的,整个脚本作为一个整体执行,中间不会被其他命令打断,从而避免了竞态条件。

-- 正确示例:使用 Lua 脚本原子释放锁 if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end

这段脚本将「判断 value 是否匹配」和「删除锁」合并为一个原子操作,从根本上杜绝了误删锁的可能。

4. 误删锁的典型场景

如果不使用 Lua 脚本,可能出现以下误删场景:

  • 线程 A获取锁,value 为 A 的标识。
  • 线程 A业务执行超时,锁自动过期。
  • 线程 B获取锁,value 为 B 的标识。
  • 线程 A此时才执行删除操作,由于没有校验 value,直接把线程 B 的锁删掉了。

使用 Lua 脚本后,线程 A 删除时会校验 value 是否仍为 A 的标识,发现不匹配则不会删除,从而保护了线程 B 的锁。这两个操作是连在一起的,因此并不会有其他的线程插入这两个操作的中间,导致误删锁的情况发生。

5. 总结

释放锁时使用 Lua 脚本,本质上是把「校验 + 删除」封装为原子操作,避免并发环境下的误删问题。这也是 Redisson 等成熟分布式锁框架内部采用 Lua 脚本的原因。理解这一点,有助于在实际项目中写出更健壮的分布式锁代码。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进