[Distributed Lock] 03-Redisson for RedLock principles

Posted by Shendemiar on Sat, 27 Jun 2020 02:29:15 +0200

Preface

You've learned the principles of Redission reentrant locks and fair locks before, and then you'll see how Redission implements RedLock.

RedLock principle

RedLock is a redis-based distributed lock that guarantees the following features:

  • Mutual exclusion: At any time, only one client can hold a lock; avoid deadlocks:
  • When the client gets the lock, no deadlock will occur even if the network partition or client downtime occurs; (using the key's lifetime)
  • Fault Tolerance: As long as the redis instances of most nodes are functioning properly, they can provide services, lock or release locks;

The idea of the RedLock algorithm is that locks cannot be created on only one redis instance. Locks should be created on multiple redis instances, n / 2 + 1. Locks must be successfully created on most redis nodes in order to calculate the overall success of RedLock locks, avoiding the problem of locking on only one redis instance.

Here's a more thorough article on RedLock parsing the last few days:
https://mp.weixin.qq.com/s/gOYWLg3xYt4OhS46woN_Lg

Redisson Implementation Principle

There is a MultiLock concept in Redisson that combines multiple locks into one large lock, unifying an application lock and releasing a lock

RedLock implementation in Redisson is based on MultiLock, so let's take a look at the implementation.

RedLock use case

First look at the official code use:
(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();
   }
}

Here is to lock three redis instances separately and get a final lock result.

RedissonRedLock Implementation Principle

Use in the example aboveRedLock.lock() or tryLock() ultimately executes the method in RedissonRedLock.

RedissonRedLock inherits from RedissonMultiLock and implements some of these methods:

public class RedissonRedLock extends RedissonMultiLock {
    public RedissonRedLock(RLock... locks) {
        super(locks);
    }

    /**
     * Number of times locks can fail, number of locks - minimum number of successful clients
     */
    @Override
    protected int failedLocksLimit() {
        return locks.size() - minLocksAmount(locks);
    }
    
    /**
     * Number of locks / 2 + 1, e.g. 3 clients lock, then at least 2 clients are required to lock successfully
     */
    protected int minLocksAmount(final List<RLock> locks) {
        return locks.size()/2 + 1;
    }

    /** 
     * Calculates the time-out for locks held together by multiple clients, and the wait time for each client
     * remainTime Default 4.5s
     */
    @Override
    protected long calcLockWaitTime(long remainTime) {
        return Math.max(remainTime / locks.size(), 1);
    }
    
    @Override
    public void unlock() {
        unlockInner(locks);
    }

}

noticeLocks.size()/2 + 1, for example, if we have three client instances, then at least two instances of successful locking will count as successful distributed locking.

Next, let's look at the implementation of lock()

RedissonMultiLock Implementation Principle

```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) {
            // If the wait time is set, the wait time will be * 2
            newLeaseTime = unit.toMillis(waitTime)*2;
        }
        
        // time For the current timestamp
        long time = System.currentTimeMillis();
        long remainTime = -1;
        if (waitTime != -1) {
            remainTime = unit.toMillis(waitTime);
        }
        // Calculate the wait time for the lock, RedLock Medium: If remainTime=-1,that lockWaitTime 1
        long lockWaitTime = calcLockWaitTime(remainTime);
        
        // RedLock in failedLocksLimit mean n/2 + 1
        int failedLocksLimit = failedLocksLimit();
        List<RLock> acquiredLocks = new ArrayList<RLock>(locks.size());
        // Loop each redis Client, to get the lock
        for (ListIterator<RLock> iterator = locks.listIterator(); iterator.hasNext();) {
            RLock lock = iterator.next();
            boolean lockAcquired;
            try {
                // call tryLock Method to acquire a lock, if the lock is acquired successfully, 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;
            }
            
            // If the lock is acquired successfully, add the lock to the list In collection
            if (lockAcquired) {
                acquiredLocks.add(lock);
            } else {
                // If acquiring a lock fails, determine if the number of failures equals the limit of failures
                // For example, three redis Client, can fail at most once
                // Here locks.size = 3, 3-x=1,Explain that once you've succeeded twice you can go straight ahead break Drop Cycle
                if (locks.size() - acquiredLocks.size() == failedLocksLimit()) {
                    break;
                }

                // If the maximum number of failures equals 0
                if (failedLocksLimit == 0) {
                    // Release all locks, RedLock Failed to lock
                    unlockInner(acquiredLocks);
                    if (waitTime == -1 && leaseTime == -1) {
                        return false;
                    }
                    failedLocksLimit = failedLocksLimit();
                    acquiredLocks.clear();
                    // Reset Iterator Retry to Acquire Lock Again
                    while (iterator.hasPrevious()) {
                        iterator.previous();
                    }
                } else {
                    // Limit the number of failures by one
                    // Like three redis Instance, the maximum limit is 1, if the first one is traversed redis Instance, failed, then failedLocksLimit Will be reduced to 0
                    // If failedLocksLimit Will walk up if Logic, release all locks, then return 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;
    }
}

 

 

The core code has been commented out, and the implementation principle is very simple. Based on the idea of RedLock, all Redis clients are traversed, then the locks are locked in turn, and the number of successful locks is counted to determine whether the locks are successful or not.

Statement

This article starts with my blog: https://www.cnblogs.com/wang-meng And Public Number: A flower is not romantic, if reproduced, please indicate the source!

Interested little partners can focus on their personal public numbers: a flower is not romantic

Topics: Redis network github Java