简介
说明
本文介绍为何要使用Redis的红锁(Redlock)、甚么是Redis的红锁和Redis红锁的原理。
本文用Redisson来介绍Redis红锁的用法。
Redisson 高版本会根据redisClient的模式来决定getLock返回的锁类型,如果集群模式,满足红锁的条件,则会直接返回红锁。
官网
REDIS distlock — Redis中国用户组(CRUG)
为何使用Redis的红锁
主从结构散布式锁的问题
实现Redis散布式锁的最简单的方法就是在Redis中创建一个key,这个key有一个失效时间(TTL),以保证锁终究会被自动释放掉。当客户端释放资源(解锁)的时候,会删除掉这个key。
从表面上看仿佛效果不错,但有一个严重的单点失败问题:如果Redis挂了怎样办?你可能会说,可以通过增加一个slave节点解决这个问题。但这通常是行不通的。这样做,我们不能实现资源的独享,由于Redis的主从同步通常是异步的。
在这类场景(主从结构)中存在明显的竞态:
- 客户端A从master获得到锁
- 在master将锁同步到slave之前,master宕掉了。
- slave节点被升级为master节点
- 客户端B重新的master获得到锁
- 这个锁对应的资源之前已被客户端A已获得到了。安全失效!
有时候程序就是这么巧,比如说正好一个节点挂掉的时候,多个客户端同时取到了锁。如果你可以接受这类小几率毛病,那用这个基于复制的方案就完全没有问题。否则的话,我们建议你实现下面描写的解决方案。
解决方案:使用红锁
简介
Redis中针对此种情况,引入了红锁的概念。红锁采取主节点过半机制,即获得锁或释放锁成功的标志为:在过半的节点上操作成功。
原理
在Redis的散布式环境中,我们假定有N个Redis master。这些节点完全相互独立,不存在主从复制或其他集群调和机制。之前我们已描写了在Redis单实例下怎样安全地获得和释放锁。我们确保将在每(N)个实例上使用此方法获得和释放锁。在这个样例中,我们假定有5个Redis master节点,这是一个比较公道的设置,所以我们需要在5台机器上面或5台虚拟机上面运行这些实例,这样保证他们不会同时都宕掉。
为了取到锁,客户端应当履行以下操作:
- 获得当前Unix时间,以毫秒为单位。
- 顺次尝试从N个实例,使用相同的key和随机值获得锁。
- 向Redis设置锁时,客户端应当设置一个网络连接和响应超时时间,这个超时时间应当小于锁的失效时间。
- 例如你的锁自动失效时间为10秒,则超时时间应当在5⑸0毫秒之间。这样可以免服务器端Redis已挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应当尽快尝试另外一个Redis实例。
- 客户端使用当前时间减去开始获得锁时间(步骤1记录的时间)得到获得锁使用的时间。
- 仅当从大多数(这里是3个节点)的Redis节点都取到锁,且使用的时间小于锁失效时间时,锁才算获得成功。
- 如果取到了锁,key的真正有效时间等于有效时间减去获得锁所使用的时间(步骤3计算的结果)。
- 如果由于某些缘由,获得锁失败(没有在最少N/2+1个Redis实例取到锁或取锁时间已超过了有效时间),客户端应当在所有的Redis实例上进行解锁(即使某些Redis实例根本就没有加锁成功)。
Redisson红锁实例
官网
官方github:8. 散布式锁和同步器 · redisson/redisson Wik
基于Redis的Redisson红锁RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也能够用来将多个RLock对象关联为一个红锁,每一个RLock对象实例可以来自于区别的Redisson实例。
大家都知道,如果负责贮存某些散布式锁的某些Redis节点宕机以后,而且这些锁正好处于锁住的状态时,这些锁会出现锁死的状态。为了不这类情况的产生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默许情况下,看门狗的检查锁的超时时间是30秒钟,也能够通过修改Config.lockWatchdogTimeout来另行指定。
另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS);
// 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
…
lock.unlock();
Redisson红锁原理
RedissonRedLock extends RedissonMultiLock,所以实际上,redLock.tryLock实际调用:org.redisson.RedissonMultiLock.java#tryLock(),进而调用到其同类的tryLock(long waitTime, long leaseTime, TimeUnit unit) ,入参为:tryLock(⑴, ⑴, null)
org.redisson.RedissonMultiLock.java#tryLock(long waitTime, long leaseTime, TimeUnit unit)源码以下:
final List<RLock> locks = new ArrayList<>();
/**
* Creates instance with multiple {@link RLock} objects.
* Each RLock object could be created by own Redisson instance.
*
* @param locks – array of locks
*/
public RedissonMultiLock(RLock… locks) {
if (locks.length == 0) {
throw new IllegalArgumentException(“Lock objects are not defined”);
}
this.locks.addAll(Arrays.asList(locks));
}
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long newLeaseTime = ⑴;
if (leaseTime != ⑴) {
newLeaseTime = unit.toMillis(waitTime)*2;
}
long time = System.currentTimeMillis();
long remainTime = ⑴;
if (waitTime != ⑴) {
remainTime = unit.toMillis(waitTime);
}
long lockWaitTime = calcLockWaitTime(remainTime);
/**
* 1. 允许加锁失败节点个数限制(N-(N/2+1))
*/
int failedLocksLimit = failedLocksLimit();
/**
* 2. 遍历所有节点通过EVAL命令履行lua加锁
*/
List<RLock> acquiredLocks = new ArrayList<>(locks.size());
for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {
RLock lock = iterator.next();
boolean lockAcquired;
/**
* 3.对节点尝试加锁
*/
try {
if (waitTime == ⑴ && leaseTime == ⑴) {
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) {
/**
*4. 如果获得到锁则添加到已获得锁集合中
*/
acquiredLocks.add(lock);
} else {
/**
* 5. 计算已申请锁失败的节点会不会已到达 允许加锁失败节点个数限制 (N-(N/2+1))
* 如果已到达, 就认定终究申请锁失败,则没有必要继续从后面的节点申请了
* 由于 Redlock 算法要求最少N/2+1 个节点都加锁成功,才算终究的锁申请成功
*/
if (locks.size() – acquiredLocks.size() == failedLocksLimit()) {
break;
}
if (failedLocksLimit == 0) {
unlockInner(acquiredLocks);
if (waitTime == ⑴ && leaseTime == ⑴) {
return false;
}
failedLocksLimit = failedLocksLimit();
acquiredLocks.clear();
// reset iterator
while (iterator.hasPrevious()) {
iterator.previous();
}
} else {
failedLocksLimit–;
}
}
/**
* 6.计算 目前从各个节点获得锁已消耗的总时间,如果已等于最大等待时间,则认定终究申请锁失败,返回false
*/
if (remainTime != ⑴) {
remainTime -= System.currentTimeMillis() – time;
time = System.currentTimeMillis();
if (remainTime <= 0) {
unlockInner(acquiredLocks);
return false;
}
}
}
if (leaseTime != ⑴) {
List<RFuture<Boolean>> futures = new ArrayList<>(acquiredLocks.size());
for (RLock rLock : acquiredLocks) {
RFuture<Boolean> future = ((RedissonLock) rLock).expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS);
futures.add(future);
}
for (RFuture<Boolean> rFuture : futures) {
rFuture.syncUninterruptibly();
}
}
/**
* 7.如果逻辑正常履行完则认为终究申请锁成功,返回true
*/
return true;
}
参考文章
Redis散布式锁之红锁
到此这篇关于Redis中Redisson红锁(Redlock)使用原理的文章就介绍到这了,更多相关Redis Redisson红锁内容请搜索之前的文章或继续浏览下面的相关文章希望大家以后多多支持!
文章来源:丸子建站
文章标题:Redis中Redisson红锁(Redlock)使用原理
https://www.wanzijz.com/view/64022.html