Redisson 的分布式锁基于 Redis 实现,通过原子操作、可重入设计、看门狗续期等机制保障高并发场景下的数据一致性。以下从核心机制、实现原理、典型场景图解及缺陷分析展开详解:
核心机制与实现原理
1.获取锁
2.加锁机制(Lua脚本原子操作)
if redis.call('exists', KEYS[1]) == 0 then -- 锁不存在
redis.call('hset', KEYS[1], ARGV[2], 1) -- 创建Hash:KEYS[1]=锁名,ARGV[2]=客户端ID+线程id,Value=1(锁重入次数)
redis.call('pexpire', KEYS[1], ARGV[1]) -- ARGV[1]=设置过期时间(默认30s)
return nil
end
if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then -- 锁存在且为当前线程
redis.call('hincrby', KEYS[1], ARGV[2], 1) -- 重入次数+1
redis.call('pexpire', KEYS[1], ARGV[1]) -- 刷新过期时间
return nil
end
return redis.call('pttl', KEYS[1]) -- 返回锁剩余生存时间(供其他线程等待)
3. 可重入性
同一线程多次加锁时,Hash
中的 Value 值递增(1
→ 2
→3...),解锁时递减至 0 才删除锁 Key;
可重入性降低锁的竞争,提高系统性能
4.锁互斥性与等待重试
其他线程加锁:若锁已被占用,返回剩余生存时间(TTL),客户端进入
while
循环自旋,并通过 Redis 的pub/sub
订阅解锁消息。唤醒机制:锁释放时发布消息,通过释放信号量唤醒等待线程竞争锁(避免无效轮询)
5. 解锁流程
if redis.call('hexists', KEYS[1], ARGV[3]) == 0 then -- 非当前线程的锁
return nil
end
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1) -- 重入次数-1
if counter > 0 then
redis.call('pexpire', KEYS[1], ARGV[2]) -- 重置过期时间
return 0
else
redis.call('del', KEYS[1]) -- 删除Key
redis.call('publish', KEYS[2], ARGV[1]) -- 发布解锁消息
return 1
end
6.锁超时续期
看门狗自动续期(Watchdog)
触发条件:未显式设置 leaseTime
时启用
流程:
加锁成功后启动后台线程,每隔 10秒(默认)检查锁是否被当前线程持有。
若持有,则重置锁过期时间为 30秒
未显式设置
leaseTime
(或leaseTime = -1
):启用看门狗机制当你使用
lock()
方法加锁时,默认行为就是 启用看门狗。此时,Redisson 会为锁设置一个默认的锁超时时间(通常是
lockWatchdogTimeout
配置的值,默认 30 秒)。看门狗机制(一个后台线程)会在锁持有期间,定期(默认在锁超时时间的 1/3 时,即 10 秒后开始)检查客户端是否仍然持有该锁。如果客户端还持有锁,看门狗会自动将锁的超时时间重置回默认值(30秒),从而避免锁因为业务执行时间过长而意外过期释放。
这种模式适用于不知道业务具体需要执行多久,希望锁能一直被持有直到业务完成并显式调用
unlock()
的场景。看门狗确保了只要客户端进程还“活着”且持有锁意图,锁就不会自动过期。
显式设置
leaseTime > 0
:禁用看门狗机制当你使用
lock(long leaseTime, TimeUnit unit)
或tryLock(long waitTime, long leaseTime, TimeUnit unit)
等方法,并明确指定了一个大于 0 的leaseTime
值时,Redisson 会禁用看门狗机制。此时,你设置的
leaseTime
就是锁的绝对生存时间。锁会在加锁成功后的leaseTime
时间后自动过期释放,无论业务逻辑是否执行完毕,也无论客户端是否仍然存活。后台没有线程会去自动续期这个锁。
这种模式适用于能够预估业务最大执行时间的场景。你需要确保设置的
leaseTime
大于 业务逻辑可能执行的最长时间,否则锁可能在业务完成前就自动释放,导致数据不一致。同时,业务逻辑执行完毕后,最好还是显式调用unlock()
来及时释放锁,避免无谓的等待。
风险:业务死循环可能导致锁永久占用