☆* o(≧▽≦)o *☆嗨~我是小奥🍹
📄📄📄个人博客:小奥的博客
📄📄📄CSDN:个人CSDN
📙📙📙Github:传送门
📅📅📅面经分享(牛客主页):传送门
🍹文章作者技术和水平有限,如果文中出现错误,希望大家多多指正!
📜 如果觉得内容还不错,欢迎点赞收藏关注哟! ❤️
文章目录
Redisson分布式锁源码篇
Redisson 是一个简单的Redis Java客户端,具有内存数据网格功能。
它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。
GitHub地址:https://github.com/redisson/redisson
Redisson分布式锁不仅是相对成熟的分布式锁方案,而且在很多企业中都会去使用的,所以了解一下底层的实现还是很有必要的。
一、使用Redisson分布式锁
Redisson分布式锁的使用非常简单,我们只需要在maven中引入依赖,然后直接调用相关API即可。
1.1 引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
1.2 调用API
// 创建RedissonClient
RedissonClient redissonClient = Redisson.create();
// 创建分布式锁
RLock lock = redissonClient.getLock("lock");
// 加锁
lock.lock();
// 尝试获取锁,无参表示不等待立即返回
boolean b = lock.tryLock();
// 解锁
lock.unlock();
二、源码解析
下面我就带大家去分析一下Redisson的可重入和可重试的源码。
2.1 可重入锁原理
(1) 原理解释
Redisson分布式锁的可重入锁的原理:
- 使用Redis中Hash的
key-value
结构,存储线程标识和重入次数; - 每次获取锁的时候,如果线程标识相同,就给Hash的value加1;
- 每次释放锁的时候,并不会删除锁,而是重入次数减1;
- 当所有逻辑执行完,如果重入次数为0,则删除锁。
逻辑流程图
(2) 源码分析
① 尝试获取锁
// 尝试获取锁,无参表示不等待立即返回
boolean b = lock.tryLock();
org.redisson.RedissonLock
@Override
public boolean tryLock() {
return get(tryLockAsync());
}
@Override
public RFuture<Boolean> tryLockAsync() {
return tryLockAsync(Thread.currentThread().getId());
}
@Override
public RFuture<Boolean> tryLockAsync(long threadId) {
// tryAcquireOnceAsync有三个参数
// long waitTime 锁重试的最大等待时间
// long leaseTime 锁自动释放时间
// TimeUnit unit 时间单位
// long threadId 线程id
return tryAcquireOnceAsync(-1, -1, null, threadId);
}
注意,下面的代码我们只需要关注有注释的部分,剩余的部分会在后面讲到。
// 根据tryAcquireOnceAsync命名可知,这里获取锁只会获取一次,不会进行重试
private RFuture<Boolean> tryAcquireOnceAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
// 判断leaseTime是否为-1(leaseTime为锁自动释放的时间,如果不传默认为-1)
if (leaseTime != -1) {
return tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
}
// tryLockInnerAsync是尝试获取锁的方法
RFuture<Boolean> ttlRemainingFuture = tryLockInnerAsync(waitTime,
commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e != null) {
return;
}
// lock acquired
if (ttlRemaining) {
scheduleExpirationRenewal(threadId);
}
});
return ttlRemainingFuture;
}
tryLockInnerAsync()
方法是获取锁的主要实现,我们可以看到,其实都是固定的Lua脚本逻辑。
// 尝试获取锁的逻辑(重点)
<T> RFuture<T> tryLockInnerAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
internalLockLeaseTime = unit.toMillis(leaseTime);
return evalWriteAsync(getName(), 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(getName()), internalLockLeaseTime, getLockName(threadId));
}
我们把这段Lua脚本摘出来分析一下逻辑:
-- 判断锁是否存在
if (redis.call('exists', KEYS[1]) == 0) then
-- 等于0表示不存在,创建锁,设置过期时间
redis