Analysis of redisson lock source code

Distributed lock

In general, the essence of implementing redis distributed locks is to ensure that the current thread monopolizes resources for a period of time (this certain time is for fault tolerance)
http://redis.cn/topics/distlock.html
Safety and activity failure guarantee

  • The simplest algorithm only needs three features to realize a minimum guaranteed distributed lock.
    • Safety property: exclusive (mutually exclusive). At any one time, only one client holds the lock.
    • Liveness property a: no deadlock. Even if the client holding the lock crashes or the network gets partitioned, the lock can still be obtained.
    • Liveness property B: fault tolerance. As long as most Redis nodes are alive, the client can acquire and release locks

Posture of distributed locks in single instance redis

Correct method of implementing distributed lock for single Redis instance
Before trying to overcome the above limitations of single instance setting, let's discuss the correct way to implement distributed locking in this simple case. In fact, this is a feasible scheme. Although there is competition, the result is still acceptable. In addition, the single instance locking method discussed here is also the basis of distributed locking algorithm.

Command for obtaining lock:

SET resource_name my_random_value NX PX 30000

This command can be executed successfully only when there is no key (NX option), and the key has an automatic expiration time of 30 seconds (PX attribute). The value of this key is "my_random_value" (a random value). This value must be unique in all clients, and the value of all acquirers (competitors) of the same key cannot be the same.

The value of value must be a random number, mainly to release the lock more safely. When releasing the lock, use the script to tell Redis: only the key exists and the stored value is the same as the value I specified can tell me that the deletion is successful. It can be realized through the following Lua script:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

Redisson lock

redissonLock implements the lock interface of java and can operate distributed locks like the lock interface in jdk
RLock objects fully comply with the Java Lock specification. That is, only processes with locks can be unlocked, and other processes will throw an IllegalMonitorStateException error when unlocking. (therefore, exceptions may be thrown when unlocking)

usage method

  • The most common method is the same as operating java lock
RLock lock = redisson.getLock("anyLock");
// Most common usage
try{
lock.lock();
// Business code 
}finally{
lock.unlock();
}

  • It takes time to acquire the lock. You can add the waiting time
RLock lock = redisson.getLock("anyLock");
// Automatically unlock 10 seconds after locking
// There is no need to call the unlock method to unlock manually
lock.lock(10, TimeUnit.SECONDS);

// Try to lock, wait for 100 seconds at most, and unlock automatically 10 seconds after locking
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
   try {
     ...
   } finally {
       lock.unlock();
   }
}
  • There are many other interesting uses, such as fair lock, redlock (distributed redis scenario), read-write lock, multilock, etc
    See details https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8

redisson, distributed lock, principle

The core method is getlock, lock and unlock, focusing on the implementation class RedissonLock. redisson's lock is generally asynchronous, which is related to the connector he implements, just like lettuce
The lock process uses a hash structure. The field in the hash is the connection id + thread id of the current lock. It supports reentrant lock
Like this

  • anyLockname
    01291287_ business thread 1
    01291287_ business thread 2

In addition, the publish ssubscribe mode is used to monitor the release of the lock, because only when the lock cannot be robbed, can you monitor when it will be released, so that you can rob it again

The following are explained in order

RLock lock = redisson.getLock("anyLock");


This parameter "anyLock" is the key of the subsequent hash structure
internalLockLeaseTime the release time of the lock (that is, the validity period of the key)
pubSub is used to listen

lock.lock() -- play

Execution process

  • The current thread obtains the lock first
    • If the lock is obtained successfully, it will be returned directly
    • Failed to acquire lock
 private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
        long threadId = Thread.currentThread().getId();
        //
        Long ttl = tryAcquire(-1, leaseTime, unit, threadId); //Get lock. If not, ttl is empty
        // lock acquired
        if (ttl == null) { //Indicates successful locking
            return;
        }
        //This indicates that locking failed
        RFuture<RedissonLockEntry> future = subscribe(threadId); //Asynchronously subscribe to the current key. ThreadID is only useful when fair lock is applied
        if (interruptibly) { //Whether it supports interrupting the following synchronous subscription (in fact, there is a default subscription time, and an error will be reported when it times out to prevent exceptions or stuck for too long)
            commandExecutor.syncSubscriptionInterrupted(future);
        } else {
            commandExecutor.syncSubscription(future);
        }
       //Here, it means that the key has been released and can grab the lock
        try {
            while (true) {
                ttl = tryAcquire(-1, leaseTime, unit, threadId); // Or call the previous method to grab the lock
                // lock acquired
                if (ttl == null) {  // If you succeed, stop and jump out
                    break;
                }

                // waiting for message
                if (ttl >= 0) {  //It was robbed by others
                    try {
                        future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    } catch (InterruptedException e) {
                        if (interruptibly) {
                            throw e;
                        }
                        future.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
                    }
                } else { //< 0 / / in Redis 2.6 and earlier versions, if the key does not exist or the key exists and has no expiration time, it will return - 1.
                   //  Since Redis 2.8, the error return value has sent the following changes:
                  //  If the key does not exist, - 2 is returned
                 //     If the key exists and has no expiration time, - 1 is returned
                    if (interruptibly) {
                        future.getNow().getLatch().acquire();
                    } else {
                        future.getNow().getLatch().acquireUninterruptibly();
                    }
                }
            }
        } finally {  //I didn't grab the lock, so I kept circling in while true
            unsubscribe(future, threadId);  // Unsubscribe
        }
//        get(lockAsync(leaseTime, unit));
    }

Added by gevo12321 on Tue, 23 Nov 2021 06:14:58 +0200