Redisson
本文章对应的代码地址:https://github.com/zhangshilin9527/redisson-study
背景
接着上一篇文章《redis分布式锁在项目中的实现一 手动实现redis分布式锁》我们继续说一下redis分布式锁,如果还想继续优化一下上面的Demo3,就需要我们引入一个新的组件Redisson。
什么是Redisson?
Redisson是最先进,最简单的Redis Java客户端,是基于Redis的对象,集合,锁,同步器和Java上分布式应用程序所需的服务。
快速开始
1.引入maven
-
<dependency>
-
<groupId>org.redisson
</groupId>
-
<artifactId>redisson
</artifactId>
-
<version>3.5.4
</version>
-
</dependency>
2.增加配置
-
@Bean
-
public RedissonClient redisson() {
-
Config config =
new Config();
-
//redis集群方式
-
config.useClusterServers().setScanInterval(
2000)
-
.addNodeAddress(
"redis://127.0.0.1:7000").addNodeAddress(
"redis://127.0.0.1:7001").setPassword(
"abcdef");
-
return Redisson.create(config);
-
}
3.代码示例
-
public String redisLockDemo4() {
-
String redisKey =
"redis_key_004";
-
RLock redissonLock = redissonClient.getLock(redisKey);
-
try {
-
redissonLock.lock();
-
//获取到redis锁,进行业务逻辑
-
System.out.println(
"获取到redis锁");
-
}
catch (Exception e) {
-
e.printStackTrace();
-
}
finally {
-
redissonLock.unlock();
-
}
-
return
"done";
-
}
redisson锁还支持读写锁,红锁等,具体其他使用方法请参照官方地址:https://redisson.org/
Redisson分布式锁原理
我使用一张图来解释一下Redisson怎么获取锁及防止锁失效的。
1.线程1,线程2,线程3同时去获取锁;
2.线程1获取到锁,线程2,线程3未获取到锁;
3.线程2,线程3while循环,进行自旋获取锁
4.线程1后台开启线程,每1/3超时时间执行一次锁延迟动作,防止锁失效。
Redisson源码解析
redisson源码使用了大量的lua脚本,使用lua脚本操作redis主要由以下几个有点:
1.减少网络开销;
2.原子操作;
3.保证事务;
不过不用担心,redisson中的lua脚本命令基本上可以看明白,看不明白的通过百度也基本可以确定,下面我们来看一下Redisson是如何加锁的。
我们Redisson是如何获取锁的,主要是通过下面这个方法
org.redisson.RedissonLock#lockInterruptibly(long, java.util.concurrent.TimeUnit)
org.redisson.RedissonLock#tryAcquireAsync
org.redisson.RedissonLock#tryLockInnerAsync
-
<T>
RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
-
internalLockLeaseTime = unit.toMillis(leaseTime);
-
-
return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
-
"if (redis.call('exists', KEYS[1]) == 0) then " +
-
"redis.call('hset', 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.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
-
}
整理一下这个流程:
1.判断是否是当前key是否存在
2.执行hset命令 key 是我们传进来的redisKey,arvg是当前线程,vaule是1
3.给锁设置时间
4.返回nil
下面的是支持重入锁,可以先不看,这样我们就获取到锁了,但是他是怎么给锁延长超时时间的呢,请看下面代码
当获取到锁之后,就会执行org.redisson.RedissonLock#scheduleExpirationRenewal方法,我们来看一下这个代码
-
private void scheduleExpirationRenewal(final long threadId) {
-
if (expirationRenewalMap.containsKey(getEntryName())) {
-
return;
-
}
-
-
Timeout task = commandExecutor.getConnectionManager().newTimeout(
new TimerTask() {
-
@Override
-
public void run(Timeout timeout) throws Exception {
-
-
RFuture<Boolean> future = commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
-
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
-
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
-
"return 1; " +
-
"end; " +
-
"return 0;",
-
Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
-
-
future.addListener(
new FutureListener<Boolean>() {
-
@Override
-
public void operationComplete(Future<Boolean> future) throws Exception {
-
expirationRenewalMap.remove(getEntryName());
-
if (!future.isSuccess()) {
-
log.error(
"Can't update lock " + getName() +
" expiration", future.cause());
-
return;
-
}
-
-
if (future.getNow()) {
-
// reschedule itself
-
scheduleExpirationRenewal(threadId);
-
}
-
}
-
});
-
}
-
}, internalLockLeaseTime /
3, TimeUnit.MILLISECONDS);
-
-
if (expirationRenewalMap.putIfAbsent(getEntryName(), task) !=
null) {
-
task.cancel();
-
}
-
}
看一下lua脚本这段,其实很简单,就几行:
1.判断当前可以是否存在
2.存在的话重新设置超时时间
3.不存在则返回0
但是需要注意的是它执行周期是超时时间的1/3,比如设置30s超时,那么每隔10s他都会执行一次锁延时任务。
转载:https://blog.csdn.net/qq_38630810/article/details/106500951