Distributed Lock-Redission

Posted by marsooka on Sat, 24 Aug 2019 04:07:40 +0200

Redission Distributed Lock

brief introduction

Redission is a Redis Official Network Distributed Solution

Official website: https://redisson.org/

github: https://github.com/redisson/redisson#quick-start

function

 

usedBy

API

 

Use

<!--Maven-->
<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.10.4</version>
</dependency> 
// 1. Create config object
Config = ...
// 2. Create Redisson instance
RedissonClient redisson = Redisson.create(config);
// 3. Get Redis based object or service you need
RMap<MyKey, MyValue> map = redisson.getMap("myMap");
​
RLock lock = redisson.getLock("myLock")
lock.lock();
//Business Code
lock.unlock();

principle

Analysis

Locking mechanism

Source analysis:

//org.redisson.RedissonLock#tryLockInnerAsync
return this.commandExecutor.evalWriteAsync(this.getName(), LongCodec.INSTANCE, command, 
    "if (redis.call('exists', KEYS[1]) == 0) then 
        redis.call('hset', KEYS[1], ARGV[2], 1); 
        redis.call('pexpire', KEYS[1], ARGV[1]); 
            return nil; 
    end; 
    if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then 
        redis.call('hincrby', KEYS[1], ARGV[2], 1); 
        redis.call('pexpire', KEYS[1], ARGV[1]); 
        return nil; 
    end; 
return redis.call('pttl', KEYS[1]);"
, Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});
    

Why use the lua language?

Because of a large number of complex business logic, this complex business logic can be guaranteed atomicity by encapsulating it in a lua script and sending it to redis

lua field explanation:

KEYS[1] represents the key you locked, for example:

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

The lock key you have set for yourself here is "myLock".

ARGV[1] represents the default lifetime of the lock key, which is 30 seconds by default.

ARGV[2] represents the ID of the locked client, similar to the following:

8743c9c0-0795-4907-87fd-6c719a6b4586:1

The first if statement is to use the "exists myLock" command to determine that if the lock key you want to lock does not exist, you will lock it.

How to lock it?Simple, use the following command:

hset myLock

​ 8743c9c0-0795-4907-87fd-6c719a6b4586:1 1

Set a hash data structure with this command, and when the command is executed, a data structure similar to the following will appear:

myLock:
    {
        8743c9c0-0795-4907-87fd-6c719a6b4586:1 1
    }

The "pexpire myLock 30000" command is then executed, setting the lifetime of the myLock lock key to 30 seconds (default)

Lock Mutex Mechanism

What if Client 2 tried to lock and executed the same lua script at this time?

Simply, the first if judgment executes "exists myLock" and discovers that the lock key myLock already exists.

Next, the second if judges whether the hash data structure of the myLock lock key contains the ID of Client 2, but obviously not because it contains the ID of Client 1.

So Client 2 gets a number returned by pttl myLock, which represents the remaining lifetime of the lock key myLock.For example, there are 15,000 milliseconds left to live.

Client 2 enters a while loop and keeps trying to lock.

watch dog automatic deferment mechanism

The default lifetime of the lock key locked by Client 1 is only 30 seconds. If it exceeds 30 seconds, Client 1 still wants to hold the lock all the time, what should I do?

Simple!As long as Client 1 successfully locks, it starts a watch dog, which is a background thread and checks every 10 seconds. If Client 1 still holds a lock key, it will continue to prolong the lifetime of the lock key.

Reentrant Locking Mechanism

If Client 1 already holds the lock, what happens to the re-entrant lock?

RLock lock = redisson.getLock("myLock")
lock.lock();
//Business Code
lock.lock();
//Business Code
lock.unlock();
lock.unlock();

Analyze the lua script above.

The first if judgment is definitely not valid, "exists myLock" will show that the lock key already exists.

The second if judgment holds because the hash data structure of myLock contains the same ID as Client 1, which is "8743c9c0-0795-4907-87fd-6c719a6b4586:1"

This executes reentrant locking logic, which he uses:

incrby myLock

8743c9c0-0795-4907-87fd-6c71a6b4586:1 1

With this command, add up to 1 the number of locks on Client 1.

The myLock data structure changes to the following:

myLock:
    {
        8743c9c0-0795-4907-87fd-6c719a6b4586:1 2
    }

Release Lock Mechanism

If lock.unlock() is executed, distributed locks can be released, and business logic is very simple.

To put it plainly, the number of locks in the myLock data structure is reduced by 1 each time.

If the number of locks is found to be zero, the client no longer holds locks, which is then used:

The "del myLock" command deletes this key from redis.

Then another client 2 can try to complete the lock.

This is the implementation mechanism of the so-called open source Redisson framework for distributed locks.

In production systems, we can use this class library provided by the Redisson framework to lock and release locks based on redis.

 

Encapsulation Tool Class

import java.util.concurrent.TimeUnit;
​
import com.caisebei.aspect.lock.springaspect.lock.DistributedLocker;
import org.redisson.api.RLock;
​
​
/**
 * redis Distributed Lock Help Class
 * @author caisebei
 *
 */
public class RedissLockUtil {
    private static DistributedLocker redissLock;
    
    public static void setLocker(DistributedLocker locker) {
        redissLock = locker;
    }
    
    /**
     * Locking
     * @param lockKey
     * @return
     */
    public static RLock lock(String lockKey) {
        return redissLock.lock(lockKey);
    }
​
    /**
     * Release lock
     * @param lockKey
     */
    public static void unlock(String lockKey) {
        redissLock.unlock(lockKey);
    }
    
    /**
     * Release lock
     * @param lock
     */
    public static void unlock(RLock lock) {
        redissLock.unlock(lock);
    }
​
    /**
     * Lock with timeout
     * @param lockKey
     * @param timeout Timeout in seconds
     */
    public static RLock lock(String lockKey, int timeout) {
        return redissLock.lock(lockKey, timeout);
    }
    
    /**
     * Lock with timeout
     * @param lockKey
     * @param unit Unit of time
     * @param timeout timeout
     */
    public static RLock lock(String lockKey, TimeUnit unit ,int timeout) {
        return redissLock.lock(lockKey, unit, timeout);
    }
    
    /**
     * Attempting to acquire a lock
     * @param lockKey
     * @param waitTime Maximum wait time
     * @param leaseTime Automatically release lock time after locking
     * @return
     */
    public static boolean tryLock(String lockKey, int waitTime, int leaseTime) {
        return redissLock.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime);
    }
    
    /**
     * Attempting to acquire a lock
     * @param lockKey
     * @param unit Unit of time
     * @param waitTime Maximum wait time
     * @param leaseTime Automatically release lock time after locking
     * @return
     */
    public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
        return redissLock.tryLock(lockKey, unit, waitTime, leaseTime);
    }
}

 

 

Advantage

Supports a variety of deployment architectures such as redis single instance, redis sentry, redis cluster, redis master-slave, etc. Based on Redis, Redis features a full range of encapsulations for use by Redis.Many companies try it out and use it in enterprise projects with high community activity.

In springboot, the automatic assembly of single machine and sentry is as follows

    /**
     * Sentry Mode Automatic Assembly
     * @return
     */
    @Bean
    @ConditionalOnProperty(name="redisson.master-name")
    RedissonClient redissonSentinel() {
        Config config = new Config();
        SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(redssionProperties.getSentinelAddresses())
                .setMasterName(redssionProperties.getMasterName())
                .setTimeout(redssionProperties.getTimeout())
                .setMasterConnectionPoolSize(redssionProperties.getMasterConnectionPoolSize())
                .setSlaveConnectionPoolSize(redssionProperties.getSlaveConnectionPoolSize());
        
        if(StringUtils.isNotBlank(redssionProperties.getPassword())) {
            serverConfig.setPassword(redssionProperties.getPassword());
        }
        return Redisson.create(config);
    }
​
    /**
     * Automatic assembly in single machine mode
     * @return
     */
    @Bean
    @ConditionalOnProperty(name="redisson.address")
    RedissonClient redissonSingle() {
        Config config = new Config();
        SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(redssionProperties.getAddress())
                .setTimeout(redssionProperties.getTimeout())
                .setConnectionPoolSize(redssionProperties.getConnectionPoolSize())
                .setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize());
        
        if(StringUtils.isNotBlank(redssionProperties.getPassword())) {
            serverConfig.setPassword(redssionProperties.getPassword());
        }
​
        return Redisson.create(config);
    }

 

 

shortcoming

The biggest problem is that if you write the value of a lock key like myLock to a redis master instance, it will be copied asynchronously to the corresponding master slave instance.

However, once the redis master is down and the master-standby switch occurs during this process, the redis slave becomes the redis master.

As a result, when Client 2 attempts to lock, it completes the lock on the new redis master, and Client 1 assumes it succeeded.

This results in multiple clients completing the lock on a distributed lock.

At this time, the system must have problems in business semantics, resulting in the generation of dirty data.

So this is the redis cluster, or the biggest disadvantage of redis distributed locks caused by master-slave asynchronous replication of the redis master architecture: when the redis master instance goes down, it can cause multiple clients to complete the locks simultaneously.

Thank you for your article on Hucea

Topics: Redis github network Maven