公司的项目需要用的分布式锁 redisson的版本
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.12.2</version>
</dependency>
问题: 公司的服务是分布式的, 4台服务器 。 4台服务器上都有定时任务的代码.导致到了规定时间点4个服务会同时运行就会引发并发问题. 然后加上 了redis 的分布式锁 。 通过redisson实现 , 但是问题并没有完全解决,同一时间点4个任务并发的问题没了又出现了 4个任务接连执行 ,为什么会这样呢 ? 这个现象加上我经验我有理由怀疑时redisson中获取锁时会进行轮循知道获取锁成功,带着疑问和猜想 我去看了看源码。
原来逻辑:
我使用的redis客户端是RedissonClient 的实现类redisson 通过getLock() 获取锁对象
org.redisson.Redisson //客户端实现类
//获取锁对象的方法
@Override
public RLock getLock(String name) {
return new RedissonLock(connectionManager.getCommandExecutor(), name);
}
这里获取的锁对象可以看到是Lock的实现类RedissonLock
然后调用尝试持有锁lock.tryLock(long time,long time,TimeUnit unit) ,如果获取成功就会执行定时任务的逻辑
if (lock.tryLock(100, 60 * 1000, TimeUnit.MILLISECONDS)) {
//业务代码
}
业务代码内有打日志,发现业务代码执行了4次,只有一个是在设定时间启动,其他都或多或少的有些延后,而我的定时任务的执行时间是大于100ms的
很明显能够看出是获取锁的时候出了问题
所以就从tryLock方法这里开始,我加了自己理解的注释
@Override
public boolean tryLock ( long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
//将 waitTime 毫秒化, 即将单位统一
long time = unit.toMillis(waitTime);
//获取当前的时间戳
long current = System.currentTimeMillis();
long threadId = Thread.currentThread().getId();
//第一次尝试加锁
Long ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
// 判断获取锁是否成功
if (ttl == null) {
return true;
}
//第一次获取锁锁失败,判断获取消耗时间是否大于等于waitTime
time -= System.currentTimeMillis() - current;
if (time <= 0) {
//超过等待时间返回false
acquireFailed(threadId);
return false;
}
//下边的代码不用看太深, 我们的问题肯定是有循环参与的,重新获取的时间戳更新current
current = System.currentTimeMillis();
RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) {
if (!subscribeFuture.cancel(false)) {
subscribeFuture.onComplete((res, e) -> {
if (e == null) {
unsubscribe(subscribeFuture, threadId);
}
});
}
acquireFailed(threadId);
return false;
}
//又判断了一次上边代码执行时间是否超过等待时间
try {
time -= System.currentTimeMillis() - current;
if (time <= 0) {
acquireFailed(threadId);
return false;
}
//重头戏来了 。 看到这个while(true)就感觉输光已经不远了
while (true) {
//再次获取当前时间戳
long currentTime = System.currentTimeMillis();
ttl = tryAcquire(leaseTime, unit, threadId);
// lock acquired
// 判断是否获取锁成功
if (ttl == null) {
return true;
}
//判断获取锁时间是否超过等待时间
time -= System.currentTimeMillis() - currentTime;
if (time <= 0) {
acquireFailed(threadId);
return false;
}
// waiting for message
// 再一次刷新currentTime的值为当前时间戳
currentTime = System.currentTimeMillis();
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(threadId);
return false;
}
}
} finally {
//取消操作
unsubscribe(subscribeFuture, threadId);
}
// return get(tryLockAsync(waitTime, leaseTime, unit));
}
从看源码可以知道,只要在while 之前没有返回 这个方法就会保证能成功获取锁对象, 只是时间问题.
但我希望就是4个有一个定时任务跑就行了 不需要每个都起来. 怎么办? 很简单让等待时间为0 。 就相当于不等待 。如果第一次获取锁对象失败就放弃 。 就ok 。
另外lock 是有一个isLock() 的方法来看锁是不是已经锁上了 。 但是如果先判断然后再决定是否尝试持有锁的话就会有并发问题 。 哈哈哈 。 细品下