Why use redistribution to solve the problem of distributed locking in high concurrency scenarios

Business scenario: implementation of inventory reduction code in high concurrency scenario

Scheme 1: use JVM or JDK level locks [synchronized]

Problem: if you use synchronized locking, there is no problem in a single machine environment, but there will be problems in a cluster / distributed environment, and it will not lock across tomcat.

@RestController
public class IndexControlelr {

    @Autowired
    private Redisson redisson;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("/deduct_stock")
    public String deductStock() {
  
        //The following code has problems in high concurrency scenarios
        synchronized(this) {
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));   //obtain redis value  jedis.setnx(key.value)
        if (stock > 0) {
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("", realStock + "");   //set up redis value   jedis.set(key.value)
            Sysytem.out.printLn("Deduction succeeded, remaining inventory:" + realStock);
        } else {
            Sysytem.out.printLn("Deduction failed, insufficient inventory");
        }
      }
        return "end";
    }
}

Scheme 2: in order to solve the problem of scheme 1, the SETNX command of redis distributed lock can solve the problem of scheme 1.

Format: setnx key value     Set the value of the key to value if and only if the key does not exist. If the given key already exists, SETNX will not do anything.

Problem: deadlock occurs when the program executes normally and the intermediate code is abnormal, resulting in the failure to release the lock. At this time, deadlock will occur.

     //use redis Distributed lock   
    @RequestMapping("/deduct_stock")
    public String deductStock() {
    
        String lockKey = "lockKey";
        boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"xl");
        if(!result){
            return "error_code";
        }
//If the following code throws an exception after execution, the lock release cannot be completed and a deadlock is generated
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); //obtain redis value jedis.setnx(key.value) if (stock > 0) { int realStock = stock - 1; stringRedisTemplate.opsForValue().set("", realStock + ""); //set up redis value jedis.set(key.value) Sysytem.out.printLn("Deduction succeeded, remaining inventory:" + realStock); } else { Sysytem.out.printLn("Deduction failed, insufficient inventory"); } //Release lock stringRedisTemplate.delete(lockKey); return "end"; }

Solution 3: to solve the problem in solution 2, set the key and operation time + try... Catch... Finally release the lock

Problem: time is a problem. In high concurrency scenarios, this code can be used, but the lock added by yourself will be released by others.

    @RequestMapping("/deduct_stock")
    public String deductStock() {
        
        String lockKey = "lockKey";
        
        try {
        //Solution: add a timeout to the lock,
        //boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"xl");
        //stringRedisTemplate.expire(lockKey,10,TimeUnit.SECONDS);
        //set up key And operation time==To ensure atomicity, merge the top two lines into the bottom one
        boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"xl",10,TimeUnit.SECONDS);
        
        if(!result){
            return "error_code";
        }
        
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));   //obtain redis value  jedis.setnx(key.value)
        if (stock > 0) {
            int realStock = stock - 1; 
            stringRedisTemplate.opsForValue().set("", realStock + "");   //set up redis value  jedis.set(key.value)
            Sysytem.out.printLn("Deduction succeeded, remaining inventory:" + realStock);
        } else {
            Sysytem.out.printLn("Deduction failed, insufficient inventory");
        }
        //Solve the problem of releasing lock finally Release lock
        }funally {
        //Release lock
        stringRedisTemplate.delete(lockKey);
      }
        return "end";
    }

Solution 4: in order to solve the problem in solution 3, generate a UUID, set the UUID to the value corresponding to the lock, and the lock you add can only be released by yourself. Start a sub thread in the background for each period of time (generally, this time is 1 / 3 of the timeout you set, and the timeout is generally 30s by default) to check whether the main thread still holds the lock, If you still hold the lock, the set timeout will be postponed for 30s

Problem: the amount of code is too large

    @RequestMapping("/deduct_stock")
    public String deductStock() {
        
        String lockKey = "lockKey";
        //Generate a UUID
        String clientId = UUID.randomUUID().toString();
        
        try {
        //boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,"xl");
        //stringRedisTemplate.expire(lockKey,10,TimeUnit.SECONDS);
        //take UUID Set to the corresponding of the lock value inside
        //There will still be problems with the time here==To solve the problem, start a sub thread in the background: start a scheduled task every 10 s(Not more than 30 s,1 of the set value/3 Check whether the main thread still holds the lock,
        //If you still hold, extend this timeout(Reset 30 s),====>The amount of problem code is too large
        
        //Solution: reddison The underlying principle of the framework is the logic we are writing now.
        //use: pom You can directly import dependent packages, which can support many redis Architecture mode (master-slave, sentinel, high concurrency, etc.)
        boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey,clientId,30,TimeUnit.SECONDS);
        
        if(!result){
            return "error_code";
        }
        
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));   //obtain redis value  jedis.setnx(key.value)
        if (stock > 0) {
            int realStock = stock - 1; 
            stringRedisTemplate.opsForValue().set("", realStock + "");   //set up redis value  jedis.set(key.value)
            Sysytem.out.printLn("Deduction succeeded, remaining inventory:" + realStock);
        } else {
            Sysytem.out.printLn("Deduction failed, insufficient inventory");
        }
        //Solve the problem of releasing lock finally Release lock
        }funally {
        //Judge whether the lock is added by itself (thread) id)
        if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))){
            stringRedisTemplate.delete(lockKey);
        }
      }
        return "end";
    }
    
}

Scheme 5: in order to solve the problem of scheme 4, the redistribution framework is used

@SpringBootApplication
public class Application {

     public static void main(String[] args) {
     SpringApplication.run(Application.class,args);
     }
     
     @Bean
     public Redisson redisson() {
         Config config = new Config();
         config.useSingleServer().setAddress("redis://localhost:8769").setDatabase(0);
         return (Redisson) Redisson.create(config);
     }
}

-----------------------------------------------------------------------------------------------------
@RequestMapping("/deduct_stock")
    public String deductStock() {
        
        String lockKey = "lockKey";

        RLock redissonLock = redisson.getLock(lockKey);
        
        try {
            //The default timeout for locking is 30 s
            redissonLock.lock();   //setIfAbsent(lockKey,clientId,30,TimeUnit.SECONDS);
            
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));   //obtain redis value  jedis.setnx(key.value)
            if (stock > 0) {
                int realStock = stock - 1; 
                stringRedisTemplate.opsForValue().set("", realStock + "");   //set up redis value  jedis.set(key.value)
                Sysytem.out.printLn("Deduction succeeded, remaining inventory:" + realStock);
            } else {
            Sysytem.out.printLn("Deduction failed, insufficient inventory");
        }
    }funally {
         
        //Release lock
        redissonLock.unlock();
        }
    }
    return "end";
}

At this time, it is almost perfect to write here (predecessors planted trees and later generations enjoyed the cool -- take it quickly!), which is not as difficult as expected.

Welcome to have better solutions and exchange......

Posted by cnagra on Sun, 05 Dec 2021 15:02:17 -0800