Redis Chapter 4 implementation of distribution lock and Lua script + native code implementation
In the previous article, redistribute was introduced. The application of redistributed in distributed locks is very simple and convenient, but redistribute itself is a encapsulated framework. In this section, we explore the implementation of Redis's simple underlying distributed locks (the encapsulation of redistribute is far more complex than this. Here we only do the underlying logical understanding and analysis)
Problem to be solved: ensure that only one client can operate shared resources at the same time
Case: the number of coupons is limited and the inventory of goods is oversold. Here, take the coupon as an example
Core:
- In order to prevent the interference between multiple processes in distributed system, a distributed coordination technology is needed to schedule the processes
- Using mutual exclusion mechanism to control the access of shared resources is the problem to be solved by distributed lock
Considerations for designing distributed locks:
- exclusiveness
- In a distributed application cluster, the same method can only be executed by one thread on one machine at a time
- Fault tolerance
- Distributed locks must be released, such as client crash or network outage
- Meet reentrant, high performance and high availability
- Pay attention to the overhead and granularity of distributed locks
Redis distributed lock official document: http://www.redis.cn/commands.html#string
RedisTemplate provides setifabsent method when locking and expire setting when preventing deadlock, but there are still problems in some details.
1. False deletion of lock: the lock is set to expire for 30ms. Thread A gets the lock and runs for 40ms. The service times out. When releasing the lock, the lock of thread B, the current lock holder, is released
Solution: set the identification of the lock
2. Time difference: multiple commands are not atomic operations. setifabsent succeeds, but fails (or goes down) when setting expire, resulting in deadlock
Solution: use the atomic instruction redistemplate opsForValue(). setIfAbsent(“seckill_1”,“success”,30,TimeUnit.MILLISECONDS)
Relatively perfect solution:
- Lock + configure expiration time: ensure atomic operation
- Unlock: prevent accidental deletion and ensure atomic operation
Locking using setIfAbsent can ensure atomicity. How to ensure atomicity by unlocking, judging and deleting
The following provides a distributed lock lua script + redis native code solution
/** * Native distributed lock start * 1,Atomic locking sets the expiration time to prevent downtime deadlock * 2,Atomic unlocking: you need to judge whether it is your own lock */ @RestController @RequestMapping("/api/v1/coupon") public class CouponController { @Autowired private StringRedisTemplate redisTemplate; @GetMapping("add") public JsonData saveCoupon(@RequestParam(value = "coupon_id",required = true) int couponId){ //Join to prevent other threads from being deleted by mistake String uuid = UUID.randomUUID().toString(); String lockKey = "lock:coupon:"+couponId; lock(couponId,uuid,lockKey); return JsonData.buildSuccess(); } private void lock(int couponId,String uuid,String lockKey){ //lua script (fixed writing method) String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; Boolean nativeLock = redisTemplate.opsForValue().setIfAbsent(lockKey,uuid,Duration.ofSeconds(30)); System.out.println(uuid+"Locked state:"+nativeLock); if(nativeLock){ //Locking succeeded try{ //TODO makes relevant business logic (customized) TimeUnit.SECONDS.sleep(10L); } catch (InterruptedException e) { } finally { //Unlock Long result = redisTemplate.execute( new DefaultRedisScript<>(script,Long.class),Arrays.asList(lockKey),uuid); System.out.println("Unlock status:"+result); } }else { //Spin operation try { System.out.println("Failed to lock. Sleep for 5 seconds and spin"); TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { } //Sleep for a while before trying to get the lock lock(couponId,uuid,lockKey); } } }