Redisson实现分布式锁
Redisson作为操作Redis的客户端,提供了非常多强大的API,比如分布式锁的实现、限流
Redisson实现分布式锁的原理
分布式锁的必备条件
- 互斥性
- 防死锁
- 可重入
- 高性能
Redisson还具备
- 可重试
- 超时续约
tryLock源码(看不懂可以先看下面的原理讲解)
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long time = unit.toMillis(waitTime);
long current = System.currentTimeMillis();
long threadId = Thread.currentThread().getId();
// 获取这把锁的超时时间
Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);
// lock acquired 锁已经空闲,可以获取
if (ttl == null) {
return true;
}
// time是指距离等待时间过期还剩多少时间
time -= System.currentTimeMillis() - current;
if (time <= 0) { // deng
acquireFailed(waitTime, unit, threadId);
return false;
}
current = System.currentTimeMillis();
// 发布订阅(获取锁)
CompletableFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
try {
// 尝试获取锁
subscribeFuture.get(time, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
// 取消订阅
if (!subscribeFuture.completeExceptionally(new RedisTimeoutException(
"Unable to acquire subscription lock after " + time + "ms. " +
"Try to increase 'subscriptionsPerConnection' and/or 'subscriptionConnectionPoolSize' parameters."))) {
subscribeFuture.whenComplete((res, ex) -> {
if (ex == null) {
unsubscribe(res, threadId);
}
});
}
// 获取失败
acquireFailed(waitTime, unit, threadId);
return false;
} catch (ExecutionException e) {
acquireFailed(waitTime, unit, threadId);
return false;
}
try {
// 计算剩余时间
time -= System.currentTimeMillis() - current;
if (time <= 0) {
acquireFailed(waitTime, unit, threadId);
return false;
}
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();
if (ttl >= 0 && ttl < time) {//等待时间大于锁的超时时间,等待ttl时间
commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
} else {// 等待时间小于锁的超时时间,等待time时间
commandExecutor.getNow(subscribeFuture).getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);
}
// 继续计算剩余时间
time -= System.currentTimeMillis() - currentTime;
if (time <= 0) { // 超时,返回false
acquireFailed(waitTime, unit, threadId);
return false;
}
// 到这还没超时继续循环,直到获取到锁
}
} finally {
unsubscribe(commandExecutor.getNow(subscribeFuture), threadId);
}
return get(tryLockAsync(waitTime, leaseTime, unit));
}
互斥性原理
Redisson底层是使用Redis的setnx命令,而setnx是单线程命令,具有天然的互斥性
防死锁原理
假如一个线程获取到了锁,但是在程序执行的过程中宕机没有释放锁,导致其它线程都获取不到锁怎么办?
Redisson采用了Redis的setnx命令指定锁的同时,给锁添加了超时时间,所有我们可以看见tryLock(long waitTime, long leaseTime, TimeUnit unit)函数中给了程序员设置leaseTime(锁的超时时间)的参数
可重入原理
Redisson存储锁的结构如上图所示
其中fieid存储的是获取到锁的线程Id,而value记录的是锁的重入次数
每当线程去申请获取已经在线程手上的锁时,就会给value += 1
每当线程去释放手上的锁时,就会给value -= 1
当线程最后一次释放锁时,value = 0,意味着是真正的释放锁
可重试原理
Redisson采用信号量和PubSub功能来控制获取锁失败后的重试机制。当某个线程尝试获取锁但因其他线程持有而失败时,Redisson可以通过信号量和PubSub功能让该线程等待并唤醒,以重新尝试获取锁
超时续约原理
超时续约:为了解决锁超时失效的问题,Redisson引入了watchDog看门狗机制。每隔一段时间(releaseTime),watchDog将重置锁的超时时间,确保即使在高并发环境下,锁也能够及时续约,防止因锁超时而被错误地释放。
!!注意:看门狗的前提是没有设置leaseTime也就是锁的释放时间