Redisson加锁脚本分析
if (redis.call(\'exists\', KEYS[1]) == 0) then \" +  \"redis.call(\'hincrby\', KEYS[1], ARGV[2], 1); \" +  \"redis.call(\'pexpire\', KEYS[1], ARGV[1]); \" +  \"return nil; \" +  \"end; \" +  \"if (redis.call(\'hexists\', KEYS[1], ARGV[2]) == 1) then \" +  \"redis.call(\'hincrby\', KEYS[1], ARGV[2], 1); \" +  \"redis.call(\'pexpire\', KEYS[1], ARGV[1]); \" +  \"return nil; \" +  \"end; \" +  \"return redis.call(\'pttl\', KEYS[1]);\",
Redisson 加锁 Lua 脚本详细分析
下面我将逐行详细解析 Redisson 加锁的核心 Lua 脚本,这个脚本实现了分布式锁的获取逻辑,包括锁的初始化、可重入支持和锁竞争处理。
脚本完整代码
lua
if (redis.call(\'exists\', KEYS[1]) == 0) then redis.call(\'hincrby\', KEYS[1], ARGV[2], 1); redis.call(\'pexpire\', KEYS[1], ARGV[1]); return nil; end; if (redis.call(\'hexists\', KEYS[1], ARGV[2]) == 1) then redis.call(\'hincrby\', KEYS[1], ARGV[2], 1); redis.call(\'pexpire\', KEYS[1], ARGV[1]); return nil; end; return redis.call(\'pttl\', KEYS[1]);
参数说明
逐行解析
第一部分:锁不存在时的初始化
lua
if (redis.call(\'exists\', KEYS[1]) == 0) then redis.call(\'hincrby\', KEYS[1], ARGV[2], 1); redis.call(\'pexpire\', KEYS[1], ARGV[1]); return nil; end;
- 
redis.call(\'exists\', KEYS[1]) == 0- 
检查锁(KEYS[1])是否不存在
 - 
exists命令返回 0 表示 key 不存在 
 - 
 - 
redis.call(\'hincrby\', KEYS[1], ARGV[2], 1)- 
如果锁不存在,使用 Hash 结构初始化锁
 - 
hincrby命令在 Hash 中为字段 ARGV[2](客户端/线程标识)设置值 1 - 
这表示当前客户端/线程第一次获取该锁
 
 - 
 - 
redis.call(\'pexpire\', KEYS[1], ARGV[1])- 
为锁设置过期时间(毫秒)
 - 
防止客户端崩溃导致锁永远无法释放
 
 - 
 - 
return nil- 
返回 nil 表示获取锁成功
 
 - 
 
第二部分:锁已存在且由当前线程持有
lua
if (redis.call(\'hexists\', KEYS[1], ARGV[2]) == 1) then redis.call(\'hincrby\', KEYS[1], ARGV[2], 1); redis.call(\'pexpire\', KEYS[1], ARGV[1]); return nil; end;
- 
redis.call(\'hexists\', KEYS[1], ARGV[2]) == 1- 
检查锁是否已由当前线程持有
 - 
hexists检查 Hash 中是否存在指定字段 
 - 
 - 
redis.call(\'hincrby\', KEYS[1], ARGV[2], 1)- 
如果锁已由当前线程持有,增加重入计数
 - 
实现可重入锁的关键
 
 - 
 - 
redis.call(\'pexpire\', KEYS[1], ARGV[1])- 
更新锁的过期时间
 - 
保证业务执行期间锁不会过期
 
 - 
 - 
return nil- 
返回 nil 表示获取锁成功(重入成功)
 
 - 
 
第三部分:锁被其他线程持有
lua
return redis.call(\'pttl\', KEYS[1]);
- 
如果前两个条件都不满足(锁存在但不是当前线程持有)
 - 
返回锁的剩余生存时间(毫秒)
- 
pttl命令返回 key 的剩余生存时间 - 
客户端可以根据这个时间决定是否继续等待
 
 - 
 
脚本执行流程总结
- 
锁不存在 → 初始化锁(设置持有者为当前线程,计数=1)→ 设置过期时间 → 获取成功
 - 
锁已存在且由当前线程持有 → 增加重入计数 → 更新过期时间 → 获取成功
 - 
锁被其他线程持有 → 返回锁的剩余生存时间 → 获取失败
 
关键设计点
- 
原子性操作:整个逻辑在一个 Lua 脚本中完成,保证原子性
 - 
可重入支持:通过 Hash 结构的字段值记录重入次数
 - 
锁续期:每次获取锁(包括重入)都会更新过期时间
 - 
竞争处理:返回锁的剩余时间让客户端决定是否等待
 - 
资源标识:ARGV[2] 唯一标识客户端/线程,避免不同客户端或线程间的误释放
 
实际应用场景示例
假设:
- 
KEYS[1] = \"my_lock\"
 - 
ARGV[1] = 30000 (30秒过期时间)
 - 
ARGV[2] = \"client1_thread1\"
 
场景1:第一次获取锁
- 
锁不存在 → 初始化 Hash:
my_lock: {client1_thread1: 1} - 
设置过期时间 30秒
 - 
获取成功
 
场景2:同一线程重入
- 
锁已存在且由 client1_thread1 持有
 - 
增加计数:
my_lock: {client1_thread1: 2} - 
更新过期时间
 - 
获取成功
 
场景3:其他线程尝试获取
- 
锁已存在且由 client1_thread1 持有
 - 
返回锁的剩余生存时间(如 25000 毫秒)
 - 
获取失败
 
这个脚本是 Redisson 分布式锁实现的核心,通过 Redis 的单线程特性和 Lua 脚本的原子性,实现了高效可靠的分布式锁机制。


