Redisson 加锁解锁原理 实现源码
Redisson 加锁解锁原理及源码分析
Redisson 是一个基于 Redis 的 Java 客户端,提供了分布式锁的实现。下面我将详细分析 Redisson 的加锁和解锁原理及其核心源码实现。
一、加锁原理
1. 基本加锁流程
Redisson 的分布式锁主要通过 Lua 脚本在 Redis 中实现,主要流程如下:
-
客户端尝试在 Redis 中设置一个键值对(锁)
-
如果设置成功(键不存在),则获取锁成功
-
如果设置失败(键已存在),则等待并重试
-
锁可以设置过期时间,避免死锁
2. 可重入锁实现
Redisson 的锁是可重入的,即同一个线程可以多次获取同一把锁。这是通过 Redis 的 Hash 结构实现的:
-
Key: 锁的名称
-
Field: 客户端/线程的唯一标识
-
Value: 锁的持有计数
二、解锁原理
1. 基本解锁流程
-
客户端检查锁是否仍由自己持有
-
如果是,则减少持有计数
-
如果计数减到0,则删除锁键
-
发布解锁消息通知其他等待的客户端
2. 锁续期机制
Redisson 通过\"看门狗\"机制实现锁的自动续期:
-
获取锁成功后,启动一个定时任务
-
定时任务定期检查锁是否仍由当前线程持有
-
如果是,则延长锁的过期时间
-
如果客户端崩溃,锁最终会自动过期
三、核心源码分析
1. 加锁源码
主要实现在 RedissonLock.tryLockInnerAsync
方法中:
java
RFuture tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command) { return evalWriteAsync(getRawName(), LongCodec.INSTANCE, command, \"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]);\", Collections.singletonList(getRawName()), unit.toMillis(leaseTime), getLockName(threadId));}
这段 Lua 脚本的逻辑是:
-
检查锁是否存在(KEYS[1])
-
如果不存在,初始化锁并设置过期时间
-
如果锁已存在且由当前线程持有,增加重入计数
-
否则返回锁的剩余生存时间
2. 解锁源码
主要实现在 RedissonLock.unlockInnerAsync
方法中:
java
protected RFuture unlockInnerAsync(long threadId) { return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, \"if (redis.call(\'hexists\', KEYS[1], ARGV[3]) == 0) then \" + \"return nil;\" + \"end; \" + \"local counter = redis.call(\'hincrby\', KEYS[1], ARGV[3], -1); \" + \"if (counter > 0) then \" + \"redis.call(\'pexpire\', KEYS[1], ARGV[2]); \" + \"return 0; \" + \"else \" + \"redis.call(\'del\', KEYS[1]); \" + \"redis.call(\'publish\', KEYS[2], ARGV[1]); \" + \"return 1; \" + \"end; \" + \"return nil;\", Arrays.asList(getRawName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));}
这段 Lua 脚本的逻辑是:
-
检查锁是否由当前线程持有
-
如果不是,返回nil
-
减少持有计数
-
如果计数仍大于0,更新过期时间
-
如果计数为0,删除锁并发布解锁消息
3. 看门狗机制源码
在 RedissonLock
类中的 scheduleExpirationRenewal
方法:
java
private void scheduleExpirationRenewal(long threadId) { ExpirationEntry entry = new ExpirationEntry(); ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry); if (oldEntry != null) { oldEntry.addThreadId(threadId); } else { entry.addThreadId(threadId); renewExpiration(); }}private void renewExpiration() { ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ee == null) { return; } Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() { @Override public void run(Timeout timeout) throws Exception { ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName()); if (ent == null) { return; } Long threadId = ent.getFirstThreadId(); if (threadId == null) { return; } RFuture future = renewExpirationAsync(threadId); future.onComplete((res, e) -> { if (e != null) { log.error(\"Can\'t update lock \" + getRawName() + \" expiration\", e); return; } if (res) { // 递归调用,实现定期续期 renewExpiration(); } }); } }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); ee.setTimeout(task);}
看门狗默认每 internalLockLeaseTime / 3
(默认10秒)执行一次续期操作。
四、关键特性总结
-
原子性操作:使用 Lua 脚本保证加锁/解锁操作的原子性
-
可重入支持:通过 Hash 结构记录锁的持有线程和重入次数
-
自动续期:看门狗机制防止业务未完成时锁过期
-
公平锁支持:通过 Redis 的队列实现公平获取锁
-
锁等待机制:通过 Redis 的发布订阅实现锁释放通知
五、使用示例
java
// 获取锁RLock lock = redisson.getLock(\"myLock\");try { // 尝试加锁,最多等待100秒,上锁后10秒自动解锁 boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); if (res) { // 业务逻辑 }} finally { // 释放锁 lock.unlock();}
Redisson 的分布式锁实现非常健壮,考虑了网络分区、客户端崩溃等各种异常情况,是生产环境中可靠的分布式锁解决方案。