> 技术文档 > Redisson 加锁解锁原理 实现源码

Redisson 加锁解锁原理 实现源码


Redisson 加锁解锁原理及源码分析

Redisson 是一个基于 Redis 的 Java 客户端,提供了分布式锁的实现。下面我将详细分析 Redisson 的加锁和解锁原理及其核心源码实现。

一、加锁原理

1. 基本加锁流程

Redisson 的分布式锁主要通过 Lua 脚本在 Redis 中实现,主要流程如下:

  1. 客户端尝试在 Redis 中设置一个键值对(锁)

  2. 如果设置成功(键不存在),则获取锁成功

  3. 如果设置失败(键已存在),则等待并重试

  4. 锁可以设置过期时间,避免死锁

2. 可重入锁实现

Redisson 的锁是可重入的,即同一个线程可以多次获取同一把锁。这是通过 Redis 的 Hash 结构实现的:

  • Key: 锁的名称

  • Field: 客户端/线程的唯一标识

  • Value: 锁的持有计数

二、解锁原理

1. 基本解锁流程

  1. 客户端检查锁是否仍由自己持有

  2. 如果是,则减少持有计数

  3. 如果计数减到0,则删除锁键

  4. 发布解锁消息通知其他等待的客户端

2. 锁续期机制

Redisson 通过\"看门狗\"机制实现锁的自动续期:

  1. 获取锁成功后,启动一个定时任务

  2. 定时任务定期检查锁是否仍由当前线程持有

  3. 如果是,则延长锁的过期时间

  4. 如果客户端崩溃,锁最终会自动过期

三、核心源码分析

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 脚本的逻辑是:

  1. 检查锁是否存在(KEYS[1])

  2. 如果不存在,初始化锁并设置过期时间

  3. 如果锁已存在且由当前线程持有,增加重入计数

  4. 否则返回锁的剩余生存时间

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 脚本的逻辑是:

  1. 检查锁是否由当前线程持有

  2. 如果不是,返回nil

  3. 减少持有计数

  4. 如果计数仍大于0,更新过期时间

  5. 如果计数为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秒)执行一次续期操作。

四、关键特性总结

  1. 原子性操作:使用 Lua 脚本保证加锁/解锁操作的原子性

  2. 可重入支持:通过 Hash 结构记录锁的持有线程和重入次数

  3. 自动续期:看门狗机制防止业务未完成时锁过期

  4. 公平锁支持:通过 Redis 的队列实现公平获取锁

  5. 锁等待机制:通过 Redis 的发布订阅实现锁释放通知

五、使用示例

java

// 获取锁RLock lock = redisson.getLock(\"myLock\");try { // 尝试加锁,最多等待100秒,上锁后10秒自动解锁 boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS); if (res) { // 业务逻辑 }} finally { // 释放锁 lock.unlock();}

Redisson 的分布式锁实现非常健壮,考虑了网络分区、客户端崩溃等各种异常情况,是生产环境中可靠的分布式锁解决方案。