How springcloud Realizes Distributed Lock with Redlock

  • 2021-12-11 17:46:59
  • OfStack

Directory 1. Introduction to redlock 2. How to use redlock with java 3. References

I have written an article before, "How to implement distributed locking in springcloud distributed system? ", because I only read the relevant books and consulted the relevant information, I think that is feasible. The general idea of that article is to use setNx command in conjunction with setEx. setNx is a time-consuming operation because it needs to query whether this key exists. Even if redis has millions of qps, this operation is problematic in high concurrency scenarios. For redis to implement distributed locking, redis officially recommends redlock.

1. Introduction to redlock

Distributed locking is a very useful technique when different processes need mutually exclusive access to shared resources. There are three attributes to consider for implementing efficient distributed locks:

Security Attribute: Mutual exclusion, only 1 client holds a lock at any time
Efficiency attribute A: No deadlock
Efficiency attribute B: Fault-tolerant, as long as most redis nodes can work properly, the client can acquire and release locks.
Redlock is a distributed lock manager algorithm proposed by redis. This algorithm will be safer and more reliable than the common method like 1. The discussion of this algorithm can be seen in the official documents.

2. How to use redlock with java

Introducing redis and redisson dependencies in the pom file:


<!-- redis-->
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
  </dependency>
  <!-- redisson-->
  <dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.3.2</version>
  </dependency>

The AquiredLockWorker interface class is mainly used for the logic that needs to be processed after acquiring a lock:


/**
 * Created by fangzhipeng on 2017/4/5.
 *  Logic to be processed after acquiring a lock 
 */
public interface AquiredLockWorker<T> {
     T invokeAfterLockAquire() throws Exception;
}

DistributedLocker Get the lock management class:


/**
 * Created by fangzhipeng on 2017/4/5.
 *  Get the lock management class 
 */
public interface DistributedLocker {

     /**
      *  Acquisition lock 
      * @param resourceName   The name of the lock 
      * @param worker  Get the processing class after the lock 
      * @param <T>
      * @return  After processing the data to be returned by the specific business logic 
      * @throws UnableToAquireLockException
      * @throws Exception
      */
     <T> T lock(String resourceName, AquiredLockWorker<T> worker) throws UnableToAquireLockException, Exception;

     <T> T lock(String resourceName, AquiredLockWorker<T> worker, int lockTime) throws UnableToAquireLockException, Exception;

}

UnableToAquireLockException, exception classes that cannot acquire locks:


/**
 * Created by fangzhipeng on 2017/4/5.
 *  Exception class 
 */
public class UnableToAquireLockException extends RuntimeException {

    public UnableToAquireLockException() {
    }

    public UnableToAquireLockException(String message) {
        super(message);
    }

    public UnableToAquireLockException(String message, Throwable cause) {
        super(message, cause);
    }
}

RedissonConnector connection class:


/**
 * Created by fangzhipeng on 2017/4/5.
 *  Get RedissonClient Connection class 
 */
@Component
public class RedissonConnector {
    RedissonClient redisson;
    @PostConstruct
    public void init(){
        redisson = Redisson.create();
    }

    public RedissonClient getClient(){
        return redisson;
    }
}

The RedisLocker class, which implements DistributedLocker:


import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;

/**
 * Created by fangzhipeng on 2017/4/5.
 */
@Component
public class RedisLocker  implements DistributedLocker{

    private final static String LOCKER_PREFIX = "lock:";

    @Autowired
    RedissonConnector redissonConnector;
    @Override
    public <T> T lock(String resourceName, AquiredLockWorker<T> worker) throws InterruptedException, UnableToAquireLockException, Exception {

        return lock(resourceName, worker, 100);
    }

    @Override
    public <T> T lock(String resourceName, AquiredLockWorker<T> worker, int lockTime) throws UnableToAquireLockException, Exception {
        RedissonClient redisson= redissonConnector.getClient();
        RLock lock = redisson.getLock(LOCKER_PREFIX + resourceName);
      // Wait for 100 seconds seconds and automatically unlock it after lockTime seconds
        boolean success = lock.tryLock(100, lockTime, TimeUnit.SECONDS);
        if (success) {
            try {
                return worker.invokeAfterLockAquire();
            } finally {
                lock.unlock();
            }
        }
        throw new UnableToAquireLockException();
    }
}

Test class:


  @Autowired
    RedisLocker distributedLocker;
    @RequestMapping(value = "/redlock")
    public String testRedlock() throws Exception{

        CountDownLatch startSignal = new CountDownLatch(1);
        CountDownLatch doneSignal = new CountDownLatch(5);
        for (int i = 0; i < 5; ++i) { // create and start threads
            new Thread(new Worker(startSignal, doneSignal)).start();
        }
        startSignal.countDown(); // let all threads proceed
        doneSignal.await();
        System.out.println("All processors done. Shutdown connection");
        return "redlock";
    }

     class Worker implements Runnable {
        private final CountDownLatch startSignal;
        private final CountDownLatch doneSignal;

        Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
            this.startSignal = startSignal;
            this.doneSignal = doneSignal;
        }

        public void run() {
            try {
                startSignal.await();
                distributedLocker.lock("test",new AquiredLockWorker<Object>() {

                    @Override
                    public Object invokeAfterLockAquire() {
                        doTask();
                        return null;
                    }

                });
            }catch (Exception e){

            }
        }

        void doTask() {
            System.out.println(Thread.currentThread().getName() + " start");
            Random random = new Random();
            int _int = random.nextInt(200);
            System.out.println(Thread.currentThread().getName() + " sleep " + _int + "millis");
            try {
                Thread.sleep(_int);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " end");
            doneSignal.countDown();
        }
    }

Run the test class:

Thread-48 start
Thread-48 sleep 99millis
Thread-48 end
Thread-49 start
Thread-49 sleep 118millis
Thread-49 end
Thread-52 start
Thread-52 sleep 141millis
Thread-52 end
Thread-50 start
Thread-50 sleep 28millis
Thread-50 end
Thread-51 start
Thread-51 sleep 145millis
Thread-51 end

From the running results, in the case of asynchronous tasks, it is true that the thread can only run after acquiring the lock. In any case, this is a scheme officially recommended by redis, which has high reliability.

3. References

https://github.com/redisson/redisson

[Redis Official Document] Building Distributed Locks with Redis

A Look at the Java Distributed In-Memory Data Model (Powered by Redis)


Related articles: