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......