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]);
参数说明
KEYS[1]: 锁的名称(Redis key)
ARGV[1]: 锁的过期时间(毫秒)
ARGV[2]: 锁的唯一标识(通常由客户端ID + 线程ID组成)
逐行解析
第一部分:锁不存在时的初始化
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 脚本的原子性,实现了高效可靠的分布式锁机制。