为了提高锁的可用性,可以使用主从或者集群模式。但是这样就会遇到主从一致性的问题,即主从同步时同步未完成主节点就宕机了。此时从节点升级为主节点,但没有同步到锁,其他线程就可能获取到锁,导致出现两把锁。
对此,Redisson的做法是,使用多个主节点,获取锁时需要所有主节点都获取到锁才算成功。
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long newLeaseTime = -1;
// leaseTime大于0,说明不使用看门狗
// waitTime是最大等待时间,所以临时把获取到的锁都设置为waitTime*2,这样waitTime内获取完可以保证前面获取的锁不会过期
if (leaseTime > 0) {
if (waitTime > 0) {
newLeaseTime = unit.toMillis(waitTime)*2;
} else {
newLeaseTime = unit.toMillis(leaseTime);
}
}
long time = System.currentTimeMillis();
long remainTime = -1;
if (waitTime > 0) {
remainTime = unit.toMillis(waitTime);
}
long lockWaitTime = calcLockWaitTime(remainTime);
// 获取锁最大失败数
int failedLocksLimit = failedLocksLimit();
List<RLock> acquiredLocks = new ArrayList<>(locks.size());
// 遍历锁节点
for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {
RLock lock = iterator.next();
boolean lockAcquired;
try {
if (waitTime <= 0 && leaseTime <= 0) {
lockAcquired = lock.tryLock();
} else {
long awaitTime = Math.min(lockWaitTime, remainTime); // 剩余等待时间不够单个锁时间了就用剩余等待时间
lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS);
}
} catch (RedisResponseTimeoutException e) {
// 超时了说明锁的状态未知,为了防止死锁强行释放掉这把锁
unlockInner(Arrays.asList(lock));
lockAcquired = false;
} catch (Exception e) {
lockAcquired = false;
}
if (lockAcquired) {
acquiredLocks.add(lock); // 先存起来
} else {
if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
break; // 失败数达到上限
}
if (failedLocksLimit == 0) { // 普通MultiLock模式:失败一把就代表全部失败
unlockInner(acquiredLocks); // 将之前获取到的锁全部释放,防止死锁
if (waitTime <= 0) { // 不重试
return false;
}
failedLocksLimit = failedLocksLimit();
acquiredLocks.clear(); // 清空获取的锁
// 重置迭代器
while (iterator.hasPrevious()) {
iterator.previous();
}
} else {
failedLocksLimit--;
}
}
// 扣减获取锁的时间并判断是否超时
if (remainTime > 0) {
remainTime -= System.currentTimeMillis() - time;
time = System.currentTimeMillis();
if (remainTime <= 0) {
unlockInner(acquiredLocks);
return false;
}
}
}
// 如果有waitTime,前面将锁设置成lwaitTime*2
// 如果没有waitTime,此时第一把锁已经消耗了一定leaseTime
// 因此统一设置为leaseTime
if (leaseTime > 0) {
acquiredLocks.stream()
.map(l -> (RedissonBaseLock) l)
.map(l -> l.expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS))
.forEach(f -> f.toCompletableFuture().join());
}
return true;
}