【分布式锁】03-使用Redisson实现RedLock原理

前言

前面已经学习了Redission可重入锁以及公平锁的原理,接着看看Redission是如何来实现RedLock的。html

RedLock原理

RedLock是基于redis实现的分布式锁,它可以保证如下特性:java

  • 互斥性:在任什么时候候,只能有一个客户端可以持有锁;避免死锁:
  • 当客户端拿到锁后,即便发生了网络分区或者客户端宕机,也不会发生死锁;(利用key的存活时间)
  • 容错性:只要多数节点的redis实例正常运行,就可以对外提供服务,加锁或者释放锁;

RedLock算法思想,意思是不能只在一个redis实例上建立锁,应该是在多个redis实例上建立锁,n / 2 + 1,必须在大多数redis节点上都成功建立锁,才能算这个总体的RedLock加锁成功,避免说仅仅在一个redis实例上加锁而带来的问题。git

这里附上一个前几天对RedLock解析比较透彻的文章:
https://mp.weixin.qq.com/s/gOYWLg3xYt4OhS46woN_Lggithub

Redisson实现原理

Redisson中有一个MultiLock的概念,能够将多个锁合并为一个大锁,对一个大锁进行统一的申请加锁以及释放锁redis

而Redisson中实现RedLock就是基于MultiLock 去作的,接下来就具体看看对应的实现吧算法

RedLock使用案例

先看下官方的代码使用:
(https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers#84-redlock)网络

RLock lock1 = redisson1.getLock("lock1"); RLock lock2 = redisson2.getLock("lock2"); RLock lock3 = redisson3.getLock("lock3"); RLock redLock = anyRedisson.getRedLock(lock1, lock2, lock3); // traditional lock method
redLock.lock(); // or acquire lock and automatically unlock it after 10 seconds
redLock.lock(10, TimeUnit.SECONDS); // or wait for lock aquisition up to 100 seconds // and automatically unlock it after 10 seconds
boolean res = redLock.tryLock(100, 10, TimeUnit.SECONDS); if (res) { try { ... } finally { redLock.unlock(); } }

这里是分别对3个redis实例加锁,而后获取一个最后的加锁结果。分布式

RedissonRedLock实现原理

上面示例中使用redLock.lock()或者tryLock()最终都是执行RedissonRedLock中方法。ide

RedissonRedLock 继承自RedissonMultiLock, 实现了其中的一些方法:学习

public class RedissonRedLock extends RedissonMultiLock { public RedissonRedLock(RLock... locks) { super(locks); } /** * 锁能够失败的次数,锁的数量-锁成功客户端最小的数量 */ @Override protected int failedLocksLimit() { return locks.size() - minLocksAmount(locks); } /** * 锁的数量 / 2 + 1,例若有3个客户端加锁,那么最少须要2个客户端加锁成功 */
    protected int minLocksAmount(final List<RLock> locks) { return locks.size()/2 + 1; } /** * 计算多个客户端一块儿加锁的超时时间,每一个客户端的等待时间 * remainTime默认为4.5s */ @Override protected long calcLockWaitTime(long remainTime) { return Math.max(remainTime / locks.size(), 1); } @Override public void unlock() { unlockInner(locks); } }

看到locks.size()/2 + 1 ,例如咱们有3个客户端实例,那么最少2个实例加锁成功才算分布式锁加锁成功。

接着咱们看下lock()的具体实现

RedissonMultiLock实现原理

```java public class RedissonMultiLock implements Lock { final List<RLock> locks = new ArrayList<RLock>(); 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 = -1; if (leaseTime != -1) { // 若是等待时间设置了,那么将等待时间 * 2
            newLeaseTime = unit.toMillis(waitTime)*2; } // time为当前时间戳
        long time = System.currentTimeMillis(); long remainTime = -1; if (waitTime != -1) { remainTime = unit.toMillis(waitTime); } // 计算锁的等待时间,RedLock中:若是remainTime=-1,那么lockWaitTime为1
        long lockWaitTime = calcLockWaitTime(remainTime); // RedLock中failedLocksLimit即为n/2 + 1
        int failedLocksLimit = failedLocksLimit(); List<RLock> acquiredLocks = new ArrayList<RLock>(locks.size()); // 循环每一个redis客户端,去获取锁
        for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) { RLock lock = iterator.next(); boolean lockAcquired; try { // 调用tryLock方法去获取锁,若是获取锁成功,则lockAcquired=true
                if (waitTime == -1 && leaseTime == -1) { lockAcquired = lock.tryLock(); } else { long awaitTime = Math.min(lockWaitTime, remainTime); lockAcquired = lock.tryLock(awaitTime, newLeaseTime, TimeUnit.MILLISECONDS); } } catch (Exception e) { lockAcquired = false; } // 若是获取锁成功,将锁加入到list集合中
            if (lockAcquired) { acquiredLocks.add(lock); } else { // 若是获取锁失败,判断失败次数是否等于失败的限制次数 // 好比,3个redis客户端,最多只能失败1次 // 这里locks.size = 3, 3-x=1,说明只要成功了2次就能够直接break掉循环
                if (locks.size() - acquiredLocks.size() == failedLocksLimit()) { break; } // 若是最大失败次数等于0
                if (failedLocksLimit == 0) { // 释放全部的锁,RedLock加锁失败
 unlockInner(acquiredLocks); if (waitTime == -1 && leaseTime == -1) { return false; } failedLocksLimit = failedLocksLimit(); acquiredLocks.clear(); // 重置迭代器 重试再次获取锁
                    while (iterator.hasPrevious()) { iterator.previous(); } } else { // 失败的限制次数减一 // 好比3个redis实例,最大的限制次数是1,若是遍历第一个redis实例,失败了,那么failedLocksLimit会减成0 // 若是failedLocksLimit就会走上面的if逻辑,释放全部的锁,而后返回false
                    failedLocksLimit--; } } if (remainTime != -1) { remainTime -= (System.currentTimeMillis() - time); time = System.currentTimeMillis(); if (remainTime <= 0) { unlockInner(acquiredLocks); return false; } } } if (leaseTime != -1) { List<RFuture<Boolean>> futures = new ArrayList<RFuture<Boolean>>(acquiredLocks.size()); for (RLock rLock : acquiredLocks) { RFuture<Boolean> future = rLock.expireAsync(unit.toMillis(leaseTime), TimeUnit.MILLISECONDS); futures.add(future); } for (RFuture<Boolean> rFuture : futures) { rFuture.syncUninterruptibly(); } } return true; } }

 

 

核心代码都已经加了注释,实现原理其实很简单,基于RedLock思想,遍历全部的Redis客户端,而后依次加锁,最后统计成功的次数来判断是否加锁成功。

申明

本文章首发自本人博客:https://www.cnblogs.com/wang-meng 和公众号:壹枝花算不算浪漫,如若转载请标明来源!

感兴趣的小伙伴可关注我的公众号:壹枝花算不算浪漫

22.jpg

原文出处:https://www.cnblogs.com/wang-meng/p/12536660.html

相关文章
相关标签/搜索