Redis Distributed Lock combat

What is the distributed lock

In the case of stand-alone deployment, in order to ensure that specific business in order to perform, synchronized keyword provided by JDK, Semaphore, ReentrantLock, or we can customize based AQS lock. The case of stand-alone deployment, the lock is shared between multiple threads, but the case of distributed deployment, the lock is shared among multiple processes. Then distributed lock to ensure the uniqueness of lock resources can be shared among multiple processes.

Distributed locking feature

  • To ensure that the same method can only be a process executed in a thread in a machine at a time;
  • To ensure that the lock is reentrant (deadlock avoidance);
  • To obtain and release locks to ensure high availability;

Distributed Lock achieve

  • Lock release (finally);
  • Lock timeout settings;
  • Lock Refresh (regular tasks, each 2/3 Lock life cycle of execution);
  • If the lock timeout, and lock to prevent the deletion of other threads (other threads will get the lock), consider using thread id value value identifies the current thread releases the lock of time to determine whether the thread id of the current thread;
  • Reentrant;

Redis Distributed Lock

RedisLockRegistry

RedisLockRegistry is spring-integration-redis redis provides distributed lock implementation class. The main lock is a good achieved by way of local redis lock + lock double lock.

OBTAIN_LOCK_SCRIPT lua script is locked. KEYS [1] the representative key value of the current locks, ARGV [1] represents the current client identification, ARGV [2] representative of the expiration time.

The basic logic is: The identification KEYS [1] to get the corresponding redis client, such as client identifier already exists and ARGV [1] are equal, resetting the expiration time ARGV [2]; if the value does not exist, provided KEYS [1] corresponding to the value of ARGV [1], and the expiration time is ARGV [2].

Acquisition process is very simple lock, the lock is first acquired by a local lock (LocalLock, corresponds ReentrantLock example), and then acquire the lock by RedisTemplate redis OBTAIN_LOCK_SCRIPT script execution.

Why use a local lock it, first of all in order to lock reentrant, followed by redis service to reduce the pressure.

释放锁的过程也比较简单,第一步通过本地锁判断当前线程是否持有锁,第二步通过本地锁判断当前线程持有锁的计数。

如果当前线程持有锁的计数 > 1,说明本地锁被当前线程多次获取,这时只释放本地锁(释放之后当前线程持有锁的计数-1)。

如果当前线程持有锁的计数 = 1,释放本地锁和redis锁。

RedisLockRegistry使用如上所示。

首先定义RedisLockRegistry对应的Bean,需要依赖redis的ConnectionFactory。

然后在服务层中注入RedisLockRegistry实例。

通过lock方法和unlock方法将业务逻辑包起来,需要注意的是unlock方法要写在finally代码块中。

Redisson

Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。

充分的利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。

使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。

同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。

  

首先感受一下通过Redisson Api使用redis分布式锁。

定义RedissonBuilder,通过redis集群地址构建RedissonClient。

定义RedissonClient类型的Bean。

业务代码里,通过RedissonClient获取分布式锁。

由于对Redisson分布式锁实现原理了解的也不是很透彻,这里推荐一篇文章:Redisson 分布式锁实现分析

Redisson和RedisLockRegistry对比

  • RedisLockRegistry通过本地锁(ReentrantLock)和redis锁,双重锁实现,Redission通过Netty Future机制、Semaphore (jdk信号量)、redis锁实现。
  • RedisLockRegistry和Redssion都是实现的可重入锁。
  • RedisLockRegistry对锁的刷新没有处理,Redisson通过Netty的TimerTask、Timeout 工具完成锁的定期刷新任务。
  • RedisLockRegistry仅仅是实现了分布式锁,而Redisson处理分布式锁,还提供了了队列、集合、列表等丰富的API。

动手实现分布式锁

实现原理

本地锁(ReentrantLock)+ redis锁

获取锁lua脚本

锁刷新lua脚本

锁释放lua脚本

本地锁定义

每一个lock key对应唯一的一个本地锁

 线程标识定义

分布式环境下,每一个线程对应一个唯一标识

 锁刷新定时任务定义

通过JDK ConcurrentTaskScheduler完成定时任务执行,ScheduledFuture完成定时任务销毁。其中taskId对应线程标识。

定义分布式锁注解

分布式锁切面

通过RedisLock注解实例lockInfo获取到锁key值、锁过期时间信息。

获取锁过程

  1. 通过lockInfo.key()方法获取到锁key值,通过锁key值拿到对应的本地锁(ReentrantLock)
  2. 本地锁获取锁对象
  3. 进入获取redis锁的循环
  4. 通过缓存服务组件执行获取锁的lua脚本
  5. 如果获取到redis锁,判断当前线程是否第一次获取到锁并且开启了锁刷新,相应的注册锁刷新定时任务
  6. 如果没有获取到redis锁,休眠lockInfo.sleep()毫秒的时间,再次重试

释放锁过程

 

  1. 获取到当前锁key值对应的本地锁
  2. 判断当前线程是否为本地锁锁的持有者
  3. 如果本地锁的重入次数大于1,则只释放本地锁
  4. 如果本地锁的重入次数等于1,释放本地锁和redis锁

分布式锁测试

定义测试类,测试方法注上@RedisLock注解,制定锁的key值为 "redis-lock-test",测试方法内随机休眠。

开启20个线程,同时调用测试方法。

多线程redis分布式锁测试结果如下。

  

定义可重入测试类,方法内获取当前代理对象,递归调用测试方法。

测试方法中,调用可重入测试类注有@RedisLock的测试方法。

分布式锁可重入测试结果如下。

 

Guess you like

Origin www.cnblogs.com/hujunzheng/p/11295345.html