提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
一、redisson是什么?
Redisson是架设在Redis基础上的一个Java驻内存数据网格,他提供了许多分布式的api,接下来介绍他对分布式锁可重入,可重试,避免超时释放的实现
二、可重入
redisson实现可重入机制是借助于类似hashset数据结构完成的
在记录当前线程标识的同时也会记录当前线程的数量,在获取锁之前会判断当前线程标识,获取成功后会在原来是数量基础上+1具体流程如下
获取锁的lua脚本
local key = KEYS[1]; -- 锁的key
local threadId = ARGV[1]; -- 线程唯一标识
local releaseTime = ARGV[2]; -- 锁的自动释放时间
-- 判断当前锁是否还是被自己持有
if (redis.call('HEXISTS', key, threadId) == 0) then
return nil; -- 如果已经不是自己,则直接返回
end;
-- 是自己的锁,则重入次数-1
local count = redis.call('HINCRBY', key, threadId, -1);
-- 判断是否重入次数是否已经为0
if (count > 0) then
-- 大于0说明不能释放锁,重置有效期然后返回
redis.call('EXPIRE', key, releaseTime);
return nil;
else -- 等于0说明可以释放锁,直接删除
redis.call('DEL', key);
return nil;
end;
释放锁的流程
local key = KEYS[1]; -- 锁的key
local threadId = ARGV[1]; -- 线程唯一标识
local releaseTime = ARGV[2]; -- 锁的自动释放时间
-- 判断当前锁是否还是被自己持有
if (redis.call('HEXISTS', key, threadId) == 0) then
return nil; -- 如果已经不是自己,则直接返回
end;
-- 是自己的锁,则重入次数-1
local count = redis.call('HINCRBY', key, threadId, -1);
-- 判断是否重入次数是否已经为0
if (count > 0) then
-- 大于0说明不能释放锁,重置有效期然后返回
redis.call('EXPIRE', key, releaseTime);
return nil;
else -- 等于0说明可以释放锁,直接删除
redis.call('DEL', key);
return nil;
end;
三、可重试
利用利用信号量和PubSub功能实现等待、唤醒,获取锁失败的重试机制,进入方法后会先将时间转化为毫秒后第一次尝试获取锁并返回锁的过期时间,如果获取失败,会重新计算剩下的时间time,并以这个时间为剩下的等待时间,在这个时间内会订阅消息队列中的消息(在unlock 脚本中 有一个 redis.call(‘publish’,key[2],argv[1])),如果在time内订阅到了就会尝试获取锁并刷新time,失败则返回false
//源码
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
//1.将等待时间转换为毫秒数,获取当前的线程
long time = unit.toMillis(waitTime);
long current = System.currentTimeMillis();
long threadId = Thread.currentThread().getId();
//2.尝试获取锁,返回null 代表没有锁,返回有值标识锁的过期时间
Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
// 3.成功获取锁
if (ttl == null) {
return true;
}
//4.尝试获取锁耗时超过了等待时间,确认失败
time -= System.currentTimeMillis() - current;
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
current = System.currentTimeMillis();
//5.消息队列 订阅了其他线程释放锁的信号
//在unlock 脚本中 有一个 redis.call('publish',key[2],argv[1])
RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
//6.当这个future 在指定时间内完成,返回true,否则false
if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
if (!subscribeFuture.cancel(false)) {
//等到最大等待时间结束,还没有等到,取消订阅,返回false
subscribeFuture.onComplete((res, e) -> {
if (e == null) {
unsubscribe(subscribeFuture, threadId);
}
});
}
acquireFailed(waitTime, unit, threadId);
return false;
}
//7.再次判断时间是否超出
try {
time -= System.currentTimeMillis() - current;
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
//todo 8.开始锁重试
while (true) {
long currentTime = System.currentTimeMillis();
ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
// lock acquired
if (ttl == null) {
return true;
}
time -= System.currentTimeMillis() - currentTime;
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
// waiting for message
currentTime = System.currentTimeMillis();
//9.利用信号量来进行获取
if (ttl >= 0 && ttl < time) {
subscribeFuture.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} else {
subscribeFuture.getNow().getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
}
time -= System.currentTimeMillis() - currentTime;
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
}
} finally {
//取消订阅
unsubscribe(subscribeFuture, threadId);
}
// return get(tryLockAsync(waitTime, leaseTime, unit));
}
四、超时续约
利用watchDog(看门狗)机制,每隔一段时间(releaseTime / 3),重置超时时间
执行流程:
private <T> RFuture<Long> tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {
//假如自定义了时间
if (leaseTime != -1) {
return tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);
}
//2.看门狗默认过期时间 30s
RFuture<Long> ttlRemainingFuture = tryLockInnerAsync(waitTime,
commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(),
TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);
ttlRemainingFuture.onComplete((ttlRemaining, e) -> {
if (e != null) {
return;
}
// lock acquired
if (ttlRemaining == null) {
//更新有效期,内部源码 通过定时任务每隔10s,定时重置有效期
scheduleExpirationRenewal(threadId);
}
});
return ttlRemainingFuture;
}