redis distributed lock combat from simple to deep to solve the problem of concurrent oversold

Posted by ryan-uk on Thu, 17 Feb 2022 22:49:29 +0100

What is the purpose of redis distributed lock to solve?

In order to solve the problem that synchronized can not achieve synchronization in distributed situations, because synchronized synchronization is the method of one jvm, which can not be done by multiple JVMs.

1. Implement the inventory reduction scenario of redis basic business

Integer stock = (Integer)redisTemplate.opsForValue().get("stock");
log.info("Remaining"+stock);
if (stock<=0){
    log.info("Rush purchase failed");
}
redisTemplate.opsForValue().set("stock",stock-1);

This code implements the basic business scenario. When there is concurrency, multiple threads will get the same stock, resulting in repeated minus 1 operations and oversold

2. Realize the basic functions of redis distributed lock

String key = "lockKey";
Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1");    //setnx key 1
//If locking fails, it indicates that other threads have been locked
if (!flag){
    log.info("The system is abnormal. Please try again later");
    return;
}
try{
    Integer stock = (Integer)redisTemplate.opsForValue().get("stock");   //get key

    if (stock<=0){
        log.info("Rush purchase failed");
        return;
    }
    Long stock1 = redisTemplate.opsForValue().decrement("stock");   //decr key
    log.info("Remaining"+stock1);
}finally {
    //Prevent code errors from causing distributed locks not to be deleted
    redisTemplate.delete(key);              //del key
}

This code solves the oversold problem, but if the application goes down during business processing, the lock will be locked.

3. What about application downtime?

The solution is to add the expiration time to the redis key. Even after a period of downtime, redis will release the distributed lock

 String key = "lockKey";
        //Writing method 1
//        redisTemplate.opsForValue().setIfAbsent(key, "1");              //setnx key 1
//        redisTemplate.expire(key,10,TimeUnit.SECONDS);          //expire key 10
        //Writing method 2
        //A redis command is atomic and better than the above method
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key,"1",10, TimeUnit.SECONDS);    //setnx key 1 10

        //If locking fails, it indicates that other threads have been locked
        if (!flag){
            log.info("The system is abnormal. Please try again later");
            return;
        }
        try{
            Integer stock = (Integer)redisTemplate.opsForValue().get("stock");   //get key

            if (stock<=0){
                log.info("Rush purchase failed");
                return;
            }
            Long stock1 = redisTemplate.opsForValue().decrement("stock");   //decr key
            log.info("Remaining"+stock1);
        }finally {
            //Prevent code errors from causing distributed locks not to be deleted
            redisTemplate.delete(key);              //del key
        }

At this time, A new problem will appear. Thread A has not finished the business and the lock is released after expiration. At this time, thread B will add A new lock, and thread A will release the lock of thread B after completing the business. There is A problem with this lock.

One solution is to increase the lock a little longer, and only allow to delete the lock added by its own thread, which is fully applicable to ordinary business scenarios. However, in the case of high concurrency, for example, if the expiration time of 1 minute is set, but the system goes down after locking, this method is locked, which is extremely fatal to the system.

There is a problem of how long the expiration time is set in high concurrency scenarios. In this case, a lock renewal function needs to be added. When the lock expires and the business has not been executed, set an expiration time for the lock until the current thread business synchronization method is executed.

4. redisson is used to realize this function

The framework has solved these problems for us. I'll just use it briefly here. For specific usage, please go to the official website.

Import dependency:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.11.1</version>
</dependency>

Configuration class:

@Bean
public Redisson redisson() {
    Config config = new Config();
    // This is stand-alone mode
    config.useSingleServer().setAddress("redis://127.0.0.1:6379s").setDatabase(0);
    return (Redisson) Redisson.create(config);
}

Synchronization code:

String key = "lockKey";
//Get reisson lock
RLock lock = redisson.getLock(key);
try{
    //The default lock is unfair lock, and the lock time of 30s will be automatically renewed
    lock.lock();
    Integer stock = (Integer)redisTemplate.opsForValue().get("stock");   //get key

    if (stock<=0){
        log.info("Rush purchase failed");
        return;
    }
    Long stock1 = redisTemplate.opsForValue().decrement("stock");   //decr key
    log.info("Remaining"+stock1);
}finally {
    lock.unlock();
}

In this way, redis distributed lock is used to solve the concurrency problem.

Topics: Redis Distributed lock