暂无图片
暂无图片
暂无图片
暂无图片
1
暂无图片

redisson-RedLock原理

程序猿西蒙 2021-11-26
924


NO.1


前言

看完Redisson实现的可重入锁等原理,大家有没有思考过他的一些缺点,比如说在一主多从或者cluster的集群模式下,在加锁成功之后,集群节点主从同步的间隙,master节点宕机了,那么我们保存的锁信息是不是也就丢失了,当slave节点上位之后,其他的客户端是不是可以加锁成功呢?redis的分布式锁确实存在这种问题,那么怎么来解决呢?
redis的官方人员提供了一种RedLock的思想,在cluster集群的模式下,对集群中所有的master节点都设置锁信息,只有一半以上的节点,加锁成功,才认为加锁成功,有点类似于zookeeper的过半机制,当然这种思想,被很多大牛质疑其可行性,当然这不是我们文章的重点,这篇主要关注点是Redisson对RedLock锁的实现,对思想有质疑的小伙伴,可自行查阅相关资料

NO.2


redisson RedLock实现


在redisson的官网文档中,确实声明了RedLock的使用方式,对于RedLock的思想也进行了具体的实现,使用代码如下:
Config config = new Config();
config.useClusterServers()
.setScanInterval(2000)
    .addNodeAddress("redis://192.168.1.17:6379");
RedissonClient client = Redisson.create(config);


RLock lock1 = client.getLock("lock1");
RLock lock2 = client.getLock("lock2");
RLock lock3 = client.getLock("lock3");


RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
lock.lock();
lock.unlock();
复制
  1. 声明了多个锁的客户端,有点像前面我们学习的RedissonMultiLock

  2. 声明了一个RedissonRedLock对象来管理锁

  3. 使用lock unlock方法来进行加锁解锁操作


看到这里,发现RedissonRedLock是不是很像前面分析的RedissonMultiLock呢?那么大家猜测一下RedissonRedLock是不是就是基于RedissonMultiLock来实现的呢?动动小脑瓜,相信大家一定能猜对

那么我们来揭晓答案,没错,它就是基于RedissonMultiLock来实现的RedLock,那么我们来回想一下RedisssonMultiLock的原理是什么呢?

是不是循环的遍历每个锁,每个锁都在指定时间内尝试加锁,如果成功,记录一下加锁成功的锁对象,如果失败了呢?是不是需要判断能否允许有失败的情况,不允许就直接释放所有的锁,如果允许,则判断失败个数是否达到了边界值,未达到边界值,就遍历下一个锁对象,只要失败的个数在边界值内,那么就加锁成功了,如果到达边界值则释放掉当前拿到的所有锁,然后进入下一次轮训,重新遍历锁对象尝试加锁,直到成功获取所有的锁

那么结合redLock的思想来思考下,RedissonRedLock的原理是不是就想出来了,RedLock是类似一种过半机制,那么是不是就允许加锁失败的情况?只要保证锁集合中超过一半的锁对象,加锁成功就算成功呢?

接下来我们来分析具体代码,查看原理是不是上面我们说的这样呢?
// RedissonMultiLock.class
@Override
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
long newLeaseTime = -1;
if (leaseTime != -1) {
if (waitTime == -1) {
newLeaseTime = unit.toMillis(leaseTime);
} else {
newLeaseTime = unit.toMillis(waitTime)*2;
}
}

long time = System.currentTimeMillis();
long remainTime = -1;
if (waitTime != -1) {
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 == -1 && leaseTime == -1) {
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) {
unlockInner(acquiredLocks);
if (waitTime == -1) {
return false;
}
failedLocksLimit = failedLocksLimit();
acquiredLocks.clear();
// reset iterator
while (iterator.hasPrevious()) {
iterator.previous();
}
} else {
failedLocksLimit--;
}
}

if (remainTime != -1) {
remainTime -= System.currentTimeMillis() - time;
time = System.currentTimeMillis();
if (remainTime <= 0) {
unlockInner(acquiredLocks);
return false;
}
}
}


if (leaseTime != -1) {
acquiredLocks.stream()
.map(l -> (RedissonLock) l)
.map(l -> l.expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS))
.forEach(f -> f.syncUninterruptibly());
}

return true;
}


// RedissonRedLock重写的方法
@Override
protected int failedLocksLimit() {
return locks.size() - minLocksAmount(locks);
}


protected int minLocksAmount(final List<RLock> locks) {
return locks.size()/2 + 1;
}


@Override
protected long calcLockWaitTime(long remainTime) {
return Math.max(remainTime / locks.size(), 1);
}
复制
  1. 查看RedissonRedLock的代码,我们发现他是RedissonMultiLock的子类,并且重写了failedLocksLimit()等一系列方法

  2. failedLocksLimit方法就是表明我允许加锁失败的边界值

  3. 所以这个原理就是很简单了,和我们上面猜测的差不多

  4. 遍历锁集合,对每个锁都尝试在规定时间内加锁,如果成功,记录一下锁对象,加锁失败,则判断失败的个数是否达到边界值

  5. 未达到边界值,那么尝试获取下一把锁,只要失败的个数在边界值内,那么就加锁成功了

  6. 达到边界值,那么释放当前获取的所有锁,进行下一轮的尝试,直到成功加锁(过半机制)


在上面的示例中我们声明了不同的三把锁,可能有人会有疑问,redLock的原理不是一把锁放在不同的节点上么?

那么大家思考下,在redis的cluster集群模式下,一个相同的key会分到不同的节点上么?通过计算同一个key的存放位置,是不是总在一个slot中?既然这样,那么又怎么来保证在每个节点上加锁么? 只能声明不同的锁,争取分布在不同的节点上来保证RedLock思想的实现对不


NO.3


总结

最后我们也来质疑一下,这个思想是不是有缺陷,是否一定能保证安全呢?
当然不可能保证安全的,我们来举个例子,假如说我们有一个cluster集群,集群是5个master节点(A,B,C,D,E),5个slave节点,当我们的客端A在A,B,C三个master节点加锁成功之后,按照RedLock的思想,客户端M就是加锁成功了,此时在主从同步的间隙,A这个master宕机了,锁信息还没有同步到A的slave节点,
此时客户端N来获取锁,恰巧在A,D,E这三台机器上加锁成功,按照思想客户端B也解锁成功,此时是不是就出问题了,是不是就不能保证安全了?
所以说,针对于RedLock的思想,国外的大牛们产生了质疑,当然这不是我们这篇文章的重点,文章核心是理解Redisson是怎么实现RedLock的




点个在看你最好看



文章转载自程序猿西蒙,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论

手机用户7230
暂无图片
1年前
评论
暂无图片 0
redlock 分布式锁并不是集群模式,而是多个单实例redis服务组成分布式锁
1年前
暂无图片 点赞
评论