A brief introduction to distributed lock

1. What is distributed lock

  • In a distributed model, when there is only one copy of the data (or a limit), the locking technology needs to be used to control the number of processes that modify the data at a given time.
  • The lock in single-machine mode not only needs to ensure that the process is visible, but also needs to consider the network between the process and the lock.
  • Distributed locks can still store tokens in memory, but the memory is not allocated by a process but in common memory such as Redis and Memcache. As for the use of databases, files, and so on to do the lock and stand-alone implementation is the same, as long as the mark can be mutually exclusive on the line.

2. Conditions for distributed locks

  • In distributed systems, a method can only be executed by one thread on a machine at a time.
  • Highly available lock acquisition and lock release;
  • High-performance lock acquisition and lock release;
  • With reentrant characteristics;
  • Lock failure mechanism to prevent deadlocks;
  • It has the non-blocking lock feature, that is, if the lock is not obtained, the system returns a failure to obtain the lock.

Second, using Redis to achieve distributed lock

1. General code implementation

@RequestMapping("/deduct_stock")
public String deductStock() {
    String lockKey = "product_001";
    try {
       /*Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "aaa"); //jedis.setnx stringRedisTemplate.expire(lockKey, 30, TimeUnit.SECONDS); // Set timeout */
        // Combine set lock and set timeout to solve atomicity problems
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "aaa".10, TimeUnit.SECONDS);

        // The current key already exists, and an error is returned
        if(! result) {return "error_code";
        }

        // Business logic implementation, deduct inventory. }catch (Exception e) {
        e.printStackTrace();
    }finally {
        stringRedisTemplate.delete(lockKey);
    }
    return "end";
}
Copy the code

2. Problem analysis

As can be seen from the above code, the current lock is invalid for 10s. If the current business logic of inventory reduction takes 15s to execute, high concurrency will cause problems:

  • For thread 1, the lock (product_001) fails after 10s
  • Thread 2 also enters the current method after 10s, and locks (product_001)
  • At 15s, thread 1 removes lock from thread 2 (product_001)
  • Thread 3, can be locked…. This loop makes the actual lock meaningless

A) Scheme 1: The current thread deletes the lock placed by the current thread

@RequestMapping("/deduct_stock")
public String deductStock() {
    String lockKey = "product_001";
    // Define a unique client ID
    String clientId = UUID.randomUUID().toString();
    try {
        // To solve the atomicity problem, combine the set lock and set timeout with clientID as the value in the lock
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);

        // The current key already exists, and an error is returned
        if(! result) {return "error_code";
        }

        // Business logic implementation, deduct inventory. }catch (Exception e) {
        e.printStackTrace();
    }finally {
        // The lock will be deleted only if the value of the lock is the current clientId
        if(clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) { stringRedisTemplate.delete(lockKey); }}return "end";
}
Copy the code

This ensures that the lock removed by each thread is the same as the lock added by the current thread, but there is still an oversold problem because thread 1 has not completed its execution and the lock has reached its expiration date, while thread 2 will successfully lock

B) Option 2: Renewal lock

Define a child thread that periodically checks to see if the main thread holds the current lock, and if so extends its expiration.

C) Plan 3: Redisson

@Autowired
Redisson redisson;
@RequestMapping("/deduct_stock_redisson")
public String deductStockRedisson() {
    String lockKey = "product_001";
    RLock rlock = redisson.getLock(lockKey);
    try {
        rlock.lock();

        // Business logic implementation, deduct inventory. }catch (Exception e) {
        e.printStackTrace();
    } finally {
        rlock.unlock();
    }
    return "end";
}
Copy the code

  • Multiple threads execute the lock operation, only one thread can successfully lock, the other threads loop block.
  • After the lock is successfully added, the default lock timeout time is 30s, and the background thread is started. The background thread will check the existence of the lock held by the thread every 10 seconds. If the lock still exists, the lock timeout time is delayed, which is reset to 30s, that is, lock delay.
  • For atomicity, The Redis distributed lock layer implements atomicity with the help of Lua scripts. Lock delay is delayed by Lua at the bottom, and the delay detection time is timeout /3

3. Analysis of the problem of Redisson distributed lock

1. Primary/secondary synchronization problem

If the lock has not been asynchronously synchronized to the slave Redis node, the master node will hang. At this time, a slave node will be used as the new master node. At this time, other threads can lock, so that the error, how to do?

A) Using ZooKeeper instead of Redis

Due to the nature of the ZK cluster, it supports CP. Redis clusters support AP.

B) Using RedLock

Suppose there are three Redis nodes with no master/slave or cluster relationship between them. The client requests the lock on three nodes with the same key and random value, and the timeout period of the lock request should be shorter than the automatic lock release time. A lock is obtained when it is requested on two redis (over half). If no lock is obtained, part of the locked Redis is released.

@RequestMapping("/deduct_stock_redlock")
public String deductStockRedlock() {
    String lockKey = "product_001";
    //TODO needs to instantiate redisson client connections for different Redis instances. This is just pseudo-code simplified with a single Redisson client
    RLock rLock1 = redisson.getLock(lockKey);
    RLock rLock2 = redisson.getLock(lockKey);
    RLock rLock3 = redisson.getLock(lockKey);

    // Try to lock three redis instances
    RedissonRedLock redLock = new RedissonRedLock(rLock1, rLock2, rLock3);
    boolean isLock;
    try {
        // If 500ms cannot get the lock, the lock is considered to have failed. 10,000 ms is the lock expiration time.
        isLock = redLock.tryLock(500.10000, TimeUnit.MILLISECONDS);
        System.out.println("isLock = " + isLock);
        if (isLock) {
            // Business logic processing. }}catch (Exception e) {

    } finally {
        // Do you want to unlock it anywayredLock.unlock(); }}Copy the code

The specific use is controversial and not recommended. Redisson is recommended for high availability concurrency, and ZooKeeper is recommended for consistency.

2. Improved concurrency: segmented locking

Because Redisson is essentially converting a parallel request into a serial request. In order to solve this problem, the lock can be segmented: for example, if there are 1000 items in the second kill item 001, it can be divided into 20 segments, and 50 items can be allocated to each segment…

The above is about Redis distributed lock learning notes, hope to help you learn Redis distributed lock, like small partners can help forward + attention, thank you!