Distributed lock based on Redisson (Redis client)

1. Introduction to Redisson

  Redisson is a Java In-Memory Data Grid implemented on the basis of Redis. It not only provides a series of distributed Java commonly used objects, but also provides many distributed services. These include (BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service , Scheduler service) Redisson provides the easiest and most convenient way to use Redis. The purpose of Redisson is to promote separation of concerns from users to Redis, so that users can focus more on processing business logic.

A detailed introduction to the Redisson project can be found on the official website .

2. Redisson's RLock distributed lock

  In "Distributed Locks Based on Redis" , we implemented distributed locks through Redis's setNX+Lua method, and Redisson is a Redis client, which is a project for implementing distributed locks in java language recommended by Redis official website.

2.1, RLock hierarchy

Insert picture description here
  In Redisson, the implementation of Redisson distributed locks is based on the RLock interface, and the source code of the RLock lock interface implementation is mainly RedissonLock, and the operations such as locking and releasing locks in the source code are all done using Lua scripts, and they are very encapsulated. Perfect and ready to use out of the box.

  • RedissonWriteLock 写锁
  • RedissonReadLock 读锁
  • RedissonTransactionalLock transaction lock
  • RedissonFairLock Fair Lock
  • RedissonRedLock 红锁
2.2, RLock common interface
public interface RLock extends Lock, RLockAsync {
    
    
	//获取名称
    String getName();
    /**
     * 中断锁 表示该锁可以被中断 假如A和B同时调这个方法,A获取锁,B为获取锁,那么B线程可以通过
     * Thread.currentThread().interrupt(); 方法真正中断该线程
     */
    void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException;
	/**
     * tryLock(long waitTime, long leaseTime, TimeUnit unit)方法和tryLock()方法(在Lock接口中定义)是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,且可以自己设置锁失效时间,后者默认30s。
     * 在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
     *
     * @param waitTime 获取锁的最大的等待时间
     * @param leaseTime 锁失效时间
     * @param unit 时间单位 小时、分、秒、毫秒等
     */
    boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;
	/**
     * 加锁 上面是默认30秒这里可以手动设置锁的有效时间
     *
     * @param leaseTime 锁有效时间
     * @param unit  时间单位 小时、分、秒、毫秒等
     */
    void lock(long leaseTime, TimeUnit unit);
	//强制解锁
    boolean forceUnlock();
	/**
     * 检验该锁是否被线程使用,如果被使用返回True
     */
    boolean isLocked();
	/**
     * 检查指定线程是否获得此锁
     */
    boolean isHeldByThread(long threadId);
	/**
     * 检查当前线程是否获得此锁
     */
    boolean isHeldByCurrentThread();
	//当前线程对该锁的持有数
    int getHoldCount();
	//锁的剩余有效时间
    long remainTimeToLive();
    
}

3. Practice based on Redisson distributed lock

  Here we are based on the project in the previous article "Distributed Lock Based on Redis" for transformation.

3.1, modify the pom file

  Remove the original redis dependencies (the dependencies below are already included) and directly introduce the following dependencies.

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.14.0</version>
</dependency>
3.2, modify the test class DemoController

 &esmp;We use the single Redis node mode here. On the original basis, we only need to introduce the redisson dependency, and then modify the DemoController. First, inject the RedissonClient object through the @Autowired annotation, and then obtain the lock through the object, and then use it.

@RestController
public class DemoController {

    private Logger logger = LoggerFactory.getLogger(DemoController.class);

    @Autowired
    private RedissonClient redissonClient;

    @RequestMapping("redissonLock")
    public String testLock() {
        logger.debug("进入testLock()方法;");
        //“order"表示业务模块

        RLock rLock = redissonClient.getLock("order");
        try{
            if(rLock.tryLock(1,25, TimeUnit.SECONDS)){
                logger.debug("获取到锁了;");
                Thread.sleep(20 * 1000);
            }else{
                logger.debug("获取锁失败了;");
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        logger.debug("方法执行完成;");
        return "方法执行完成";
    }

}
3.3. Summary

  The test method of the distributed lock is similar to the previous one, so it will not be repeated here. Through the previous simple use, we can know that in fact, the distributed lock of the Redisson framework is used. We don't need to define the lock ourselves, which is a lot more convenient. At the same time, many error-prone details have been considered by the framework.

4. Watchdog

  There is a problem with the previous Redis-based distributed lock implementation and the previous Redssion-based simple use: when the lock failure time is up, but the business logic has not been executed, there will be problems, that is, because the lock fails, other threads can continue. Use the distributed lock. For this kind of problem, in the Redssion framework, the watchdog mechanism is used to handle it.

4.1. Implementation principle

  In order to avoid the occurrence of lock overdue failure, Redisson provides a watchdog that monitors the lock. Its function is to continuously extend the validity period of the lock before the Redisson instance is closed. By default, the timeout period for the watchdog to check the lock is 30 seconds, and it can also be specified by modifying Config.lockWatchdogTimeout.

4.2, test

  When using the rLock.tryLock() method without parameters, the watchdog will be automatically enabled, as shown below. To demonstrate the effect, I set the sleep time to 60s (and the lock failure time is 30s).

If you use the rLock.tryLock() method with parameters, you need to ensure that the watchdog configuration parameter lockWatchdogTimeout <leaseTime (lock failure time), otherwise the watchdog will become invalid.

 @RequestMapping("redissonLock")
 public String testLock() {
      logger.debug("进入testLock()方法;");
      //“order"表示业务模块

      RLock rLock = redissonClient.getLock("order");
      try{
          if(rLock.tryLock()){
              logger.debug("获取到锁了;");
              Thread.sleep(60 * 1000);
          }else{
              logger.debug("获取锁失败了;");
          }
      }catch (Exception e){
          e.printStackTrace();
      }
      logger.debug("方法执行完成;");
      return "方法执行完成";
  }

 &esmp; After restarting the project (8080, 8081 ports), we first visit http://localhost:8080/redissonLock, this time the thread will obtain the lock, after waiting for 35s, visit http://localhost:8081/ For the redissonLock interface, the lock failure time has passed normally. At this time, thread two should be able to acquire the lock, but the time situation is that the acquisition failed. The print log is as follows:
Insert picture description here
Insert picture description here

5. Redlock

  In the above-mentioned single-node case, it is difficult to guarantee high availability. If in the master-slave mode, sentry mode, cluster mode, there will be problems such as asynchronous data loss and split brain, then how to achieve high availability of distributed locks and avoid these problems?

  In order to solve these problems, the concept of Redlock is proposed on the Redis official website: Suppose there are 5 redis nodes, and there is neither a master-slave nor a cluster relationship between these nodes. The client uses the same key and random value to request locks on 5 nodes, and the timeout period for requesting locks should be less than the lock automatic release time. When the lock is requested on 3 (more than half) redis, the lock is truly acquired. If the lock is not acquired, part of the locked redis is released.

Refer to "Red Lock Redisson Java Implementation of Redis Distributed Lock" and "Realization of Red Lock" .

  In actual scenarios, red locks are rarely used. Because the use of red locks will affect the performance in a high-concurrency environment, making the program experience worse. Moreover, in actual scenarios, we generally want to ensure the reliability of the Redis cluster. At the same time, after using the red lock, when the number of successfully locked RLocks does not exceed half of the total, the lock failure will be returned. Even if the task lock is successful at the business level, the red lock will return the result of the lock failure. In addition, when using red locks, multiple sets of Redis master-slave deployment architectures need to be provided. At the same time, the Master nodes in these multiple Redis master-slave architectures must be independent and there is no data interaction between them.

  The usage of using Redisson framework to realize red lock is as follows:

public void testRedLock(RedissonClient redisson1,RedissonClient redisson2, RedissonClient redisson3){
    
    
    RLock lock1 = redisson1.getLock("lock1");
    RLock lock2 = redisson2.getLock("lock2");
    RLock lock3 = redisson3.getLock("lock3");
    RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
    try {
    
    
        // 同时加锁:lock1 lock2 lock3, 红锁在大部分节点上加锁成功就算成功。
        lock.lock();
        // 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
        boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
    
    
        e.printStackTrace();
    } finally {
    
    
        lock.unlock();
    }
}

Guess you like

Origin blog.csdn.net/hou_ge/article/details/112801568