Analysis and solution of repeated deduction of inventory in high concurrency scenario

1, Scene simulation

When robbing red packets or seckilling goods, there must be high concurrency. If there is repeated inventory deduction in the program, it must not work! Next, we simulate the problem of high and issued inventory repeated deduction and the corresponding solutions.

Before testing, you need to set a key for redis as inventory in advance.  

@GetMapping("/redis/stuck")
    public String getRedisStuck()  {
            //What are the problems in high concurrency scenarios?
            int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            String str="";
            if (stock>0){
                int realStock=stock-1;
                stringRedisTemplate.opsForValue().set("stock",realStock+"");
                str="Deduct successfully, remaining inventory:"+realStock;
                System.out.println(str);
            }else{
                str="No stock!";
                System.out.println(str);
            }
      return "end";
    }

Jmeter configuration

1. Add thread group:

2. Add interface:

3. Start Jmeter thread group, and the printing results of the two machines are as follows:

        

4. It can be found that when the concurrent volume is large, the inventory is not deducted!

 

2, Solutions

1. lock up

Use the setifacent () method to lock the key, and the thread can only execute after getting the lock.

   @GetMapping("/redis/stuck")
    public String getRedisStuck() throws IOException {
        String lockKey="product001";

        try{
            Boolean result=stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"HUAWEI mobile phone");
            if (!result){
                return "Please try again later!";
            }
            int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            String str="";
            if (stock>0){
                //Think about what to do if the execution goes down at this time? , finally can't execute, key is not deleted, other threads can't get the lock, resulting in deadlock.
                int realStock=stock-1;
                stringRedisTemplate.opsForValue().set("stock",realStock+"");
                str="Deduct successfully, remaining inventory:"+realStock;
                System.out.println(str);
            }else{
                str="No stock!";
                System.out.println(str);
            }

        }finally {
            //Unlock
            stringRedisTemplate.delete(lockKey);
        }
        
      return "end";
    }

Existing problems: if there is a program outage when the if, the key that gets the lock will never be released, resulting in deadlock!

2. Set failure time

Add an expiration time to the lock so that there is no problem that the lock will never be released. You can also set the expiration time using the IfAbsent() method! That is, the thread with lock can continue to execute, and the thread without lock will return first. Modify the following code:

 Boolean result=stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"HUAWEI mobile phone",10 ,TimeUnit.SECONDS);
if (!result){ 
  return "Please try again later!";
 }

The complete code is as follows:

 @GetMapping("/redis/stuck")
    public String getRedisStuck() throws IOException {
        String lockKey="product001";
        try{
            //Set expiration time
            Boolean result=stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"HUAWEI mobile phone",10 ,TimeUnit.SECONDS);
            //What are the problems in high concurrency scenarios
            if (!result){
                return "Please try again later!";
            }

            int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            String str="";
            if (stock>0){
                //Think about what to do if the execution goes down at this time? , finally, it can't be executed. The key is not deleted. Other threads can't get the lock and generate a deadlock.
                int realStock=stock-1;
                stringRedisTemplate.opsForValue().set("stock",realStock+"");
                str="Deduct successfully, remaining inventory:"+realStock;
                System.out.println(str);
            }else{
                str="No stock!";
                System.out.println(str);
            }

        }finally {
            //Unlock
                stringRedisTemplate.delete(lockKey);
        }

      return "end";
    }

Although the above change solves the deadlock problem to some extent by using the redis lock failure method, there is still the problem of lock permanent failure in high concurrency scenarios!

   

3. Use UUID

Analysis: when a thread comes in, generate a UUID for each thread, and store the UUID in the value of the lock key. Before releasing the lock, judge whether the UUID corresponding to the lock obtained from redis is equal to the UUID currently generated. If so, release the lock!

    @GetMapping("/redis/stuck")
    public String getRedisStuck() throws IOException {
        String lockKey="product001";
        String clientId= UUID.randomUUID().toString();
        try{
            //Set expiration time
            Boolean result=stringRedisTemplate.opsForValue().setIfAbsent(lockKey,clientId,10 ,TimeUnit.SECONDS);
            //What are the problems in high concurrency scenarios
            if (!result){
                return "Please try again later!";
            }

            int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            String str="";
            if (stock>0){
                //Think about what to do if the execution goes down at this time? , finally can't execute, key is not deleted, other threads can't get the lock, resulting in deadlock.
                int realStock=stock-1;
                stringRedisTemplate.opsForValue().set("stock",realStock+"");
                str="Deduct successfully, remaining inventory:"+realStock;
                System.out.println(str);
            }else{
                str="No stock!";
                System.out.println(str);
            }

        }finally {
            //Unlock
            if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
                stringRedisTemplate.delete(lockKey);
            }
        }

      return "end";
    }

This method solves the possibility that threads release locks of other threads! Problem: the performance of the program will be low.

4. Force the lock to survive

Idea: when a thread comes over, start a sub thread for the current thread, add a timer to the sub thread, execute it regularly every 1 / 3 of the lock failure time, and then judge whether the key has a lock. If there is a lock, extend the lock failure time.

5. Redisson distributed lock

The redisson distributed lock can ensure that only one thread in a cluster can get the lock, and has high performance. This method is preferred in the distributed architecture.

Add dependency:

 <!--redisson Distributed lock-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.6.5</version>
        </dependency>

Initialization at startup. In the main method class, add an @ Bean:

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

Use @ autowire annotation directly!

@Autowired
private Redisson redisson;

Use the lock() method before executing the inventory reduction. The thread that executes the lock method can continue to execute later. The thread that does not get the lock will enter the blocking state.  

 @GetMapping("/redis/stuck")
    public String getRedisStuck() throws IOException {
      String lockKey="product001";
       RLock lock= redisson.getLock(lockKey);
        try{
            lock.lock(10,TimeUnit.SECONDS);
            int stock=Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            String str="";
            if (stock>0){
                //Think about what to do if the execution goes down at this time? , finally, it can't be executed. The key is not deleted. Other threads can't get the lock and generate a deadlock.
                int realStock=stock-1;
                stringRedisTemplate.opsForValue().set("stock",realStock+"");
                str="Deduct successfully, remaining inventory:"+realStock;
                System.out.println(str);
            }else{
                str="No stock!";
                System.out.println(str);
            }
        }finally {
            //Unlock
            lock.unlock();
        }
      return "end";
    }


 

Published 82 original articles, won praise 49, visited 10000+
Private letter follow

Tags: Redis Mobile

Posted on Sun, 15 Mar 2020 03:22:25 -0700 by Naki-BoT