Redis Chapter 4 distributed lock principle + native implementation code

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);
        }
    }

}

Keywords: Java Redis Multithreading Distribution

Added by bothwell on Mon, 31 Jan 2022 21:44:49 +0200