Redisson 分布式锁的加锁和解锁原理
Redisson 是基于 Redis 实现的 Java 分布式锁框架,其加锁和解锁机制封装了复杂的 Redis 操作细节,同时提供了自动续期、可重入等高级特性。以下从加锁、解锁的核心流程及关键特性原理展开说明:
一、加锁原理
Redisson 的分布式锁基于 Redis 的Hash
数据结构和Lua
脚本实现,核心是通过原子操作保证锁的互斥性,同时支持可重入和自动续期。
1. 基本加锁流程
-
数据结构 :锁在 Redis 中以
Hash
类型存储,key
为锁名称(如stock:1001
),field
为获取锁的线程唯一标识(格式为UUID:线程ID
),value
为该线程的重入次数。 -
加锁逻辑(Lua 脚本):
vbnet
\-- 若锁不存在,则创建锁并设置重入次数为1,同时设置过期时间
if (redis.call('exists', KEYS\[1]) == 0) then
redis.call('hset', KEYS\[1], ARGV\[2], 1);
redis.call('pexpire', KEYS\[1], ARGV\[1]);
return 1;
end;
\-- 若锁已存在且属于当前线程,则重入次数+1,刷新过期时间
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 1;
end;
\-- 若锁已存在但不属于当前线程,返回锁的剩余过期时间
return redis.call('pttl', KEYS\[1]);
-
KEYS[1]
:锁名称(如stock:1001
)。 -
ARGV[1]
:锁的过期时间(默认 30 秒,可自定义)。 -
ARGV[2]
:线程唯一标识(UUID:线程ID
)。
-
加锁结果:
-
返回
1
:加锁成功(首次获取或重入)。 -
返回大于 0 的数字:加锁失败,返回当前锁的剩余过期时间(单位:毫秒)。
-
2. 关键特性原理
-
可重入性:
-
同一线程再次加锁时,通过
hexists
判断锁的field
是否为当前线程标识,若是则通过hincrby
将重入次数 + 1,并刷新过期时间。 -
解锁时需递减重入次数,直至次数为 0 才删除锁,保证同一线程的多次加锁 / 解锁操作不会提前释放锁。
-
-
自动续期(看门狗机制):
-
若未指定锁的过期时间,Redisson 会启动一个 "看门狗" 线程(
LockWatchdogTimeout
默认 30 秒)。 -
线程每 10 秒(
LockWatchdogTimeout/3
)检查一次,若锁仍被当前线程持有,则通过pexpire
延长锁的过期时间至 30 秒。 -
确保业务未执行完时,锁不会因过期被释放,避免并发问题。
-
-
公平锁机制:
-
公平锁通过 Redis 的
zset
结构维护等待队列,每个等待线程对应一个zset
的元素(score 为请求时间戳,value 为线程标识)。 -
加锁时先判断队列中是否有前驱线程,若有则进入等待;无则尝试获取锁,保证按请求顺序分配锁资源,避免线程饥饿。
-
二、解锁原理
解锁过程同样通过 Lua 脚本保证原子性,核心是校验线程标识、递减重入次数,最终删除锁并通知等待线程。
1. 基本解锁流程
- 解锁逻辑(Lua 脚本):
lua
\-- 若锁不属于当前线程,返回nil(解锁失败)
if (redis.call('hexists', KEYS\[1], ARGV\[3]) == 0) then
  return nil;
end;
\-- 重入次数-1
local counter = redis.call('hincrby', KEYS\[1], ARGV\[3], -1);
\-- 若重入次数>0,刷新过期时间,返回0(未完全释放)
if (counter > 0) then
  redis.call('pexpire', KEYS\[1], ARGV\[2]);
  return 0;
else
  \-- 若重入次数=0,删除锁,返回1(完全释放)
  redis.call('del', KEYS\[1]);
  \-- 发布锁释放通知,唤醒等待线程
  redis.call('publish', KEYS\[2], ARGV\[1]);
  return 1;
end;
-
KEYS[1]
:锁名称;KEYS[2]
:通知通道名称(如redisson_lock__channel:{lockName}
)。 -
ARGV[1]
:解锁消息;ARGV[2]
:过期时间;ARGV[3]
:线程唯一标识。
-
解锁结果:
-
若锁不属于当前线程:返回
nil
,解锁失败(避免误删其他线程的锁)。 -
若重入次数 > 0:刷新过期时间,返回
0
(仅递减次数,未删除锁)。 -
若重入次数 = 0:删除锁并通过
publish
发布通知,返回1
(完全释放锁)。
-
2. 通知机制
-
未获取到锁的线程会订阅锁对应的 Redis 通道(
redisson_lock__channel:{lockName}
),并进入阻塞等待。 -
当锁被完全释放(
del
操作后),通过publish
发送通知,唤醒等待线程重新竞争锁,避免无效的轮询消耗资源。
三、总结
Redisson 分布式锁的核心优势在于:
-
原子性:通过 Lua 脚本将加锁 / 解锁的多步 Redis 操作封装为原子操作,避免并发漏洞。
-
可靠性:结合 Redis 的过期机制和看门狗续期,防止锁永久占用;支持集群模式,降低单点故障风险。
-
易用性 :封装了可重入、公平锁、自动续期等特性,开发者无需关注底层细节,直接通过
RLock
接口调用即可。
其加锁和解锁过程紧密依赖 Redis 的Hash
、zset
等数据结构及publish/subscribe
机制,在分布式系统中高效保证了共享资源的互斥访问。