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他都会执行一次锁延时任务。