Redisson 介绍
对于分布式或者多节点应用,一个分布式锁对于多并发场景显得尤为重要。一般分布式锁要支持和满足以下特性:
1、互斥:同一时刻只能有一个线程获得锁。
2、防止死锁:分布式锁非常有必要设置锁的有效时间,确保系统出现故障后,在一定时间内能够主动去释放锁,避免造成死锁的情况。
3、性能:需要考虑减少锁等待的时间,避免导致大量线程阻塞。在锁的设计时,需要考虑两点。1、锁的颗粒度要尽量小 2、锁的范围尽量要小
4、重入:同一个线程可以重复拿到同一个资源的锁。重入锁非常有利于资源的高效利用
Redisson 作为 java 的 Redis 客户端之一,是 Redis 官网推荐的 java 语言实现分布式锁的项目。 Redisson 就是提供了一堆锁... 也是目前大部分公司使用 Redis 分布式锁最常用的一种方式。
可重入锁(Reentrant Lock)/公平锁(Fair Lock)/联锁(MultiLock)/红锁(RedLock)/读写锁(ReadWriteLock)/信号量(Semaphore) 等等
项目结构
Redisson有两个主要的接口类,RedissonClient和RLock。其中RedissonClient定义了客户端的相关方法,如创建锁。RLock则定义了锁的具体操作,若加锁,释放锁等。其中Redisson是RedissonClient的实现类,RedissonLock是RLock的实现类。当然这里只展示了几个重要的的接口和实现类。
RLock
RLock则定义了锁的具体操作,若加锁,释放锁等,详情如下
RedissonClient
RedissonClient定义了客户端的相关方法,如创建锁
加锁过程
加锁和解锁过程都是RedissonLock为例。
lock方法调用了lockInterruptibly方法,该方法翻译过来为可中断锁,若没有获取锁,则会进行阻塞等待锁。
该方法最重要的一步是调用tryAcquire来获取ttl(Time To Live,存活时间)。若为null,说明加锁成功。若ttl不为null,说明加锁失败,则会阻塞进行等到锁。具体有以下几步操作,
首先使用subscribe()给当前加锁失败的线程去订阅一个channel(当该线程调用unlock()或者interrupt()时,redis会发送事件通知,让该线程不再阻塞等待锁)。
然后进行下面的while循环,尝试加锁刷新这个ttl的时间, 分析ttl >= 0的逻辑,等待ttl秒获取许可, 假设现在ttl是10秒,这个方法就会阻塞在这里等待10s之后去循环这个while继续尝试加锁。
lock方法方法中最重要的还是调用了tryAcquire()方法来获取ttl。其中tryAcquire()分两步。调用tryAcquireAsync()方法,由于 leaseTime == -1,于是又调用 tryLockInnerAsync()方法。若leaseTime==-1,则加锁时间默认为是30s。
真正加锁的操作还是用的lua语法实现的,如下所示。其
- 根据key查询是否存在,若不存在。则是在一个 getLockName(threadId),值为1的键值对,并设置过期时间,返回null。
- 如果key查询存在,则根据getLockName(threadId)判断是否存在,若存在,则在其值上+1,并重新设置过期时间,返回null。(可重入锁)
- 否则(即key存在,且被其他线程占有),则返回ttl。
KEYS[1] 为 getName(),ARGV[2] 为 getLockName(threadId)。ARGV[1] internalLockLeaseTime,锁的有效时间。
释放锁过程
解锁过程最终还是执行的以下redis命令,其中涉及到了四个参数,分别为:
KEYS[1]:key
KEYS[2]:ChannelName,加锁的时候会生成一个channel
ARGV[1]:LockPubSub.unlockMessage,存的是0
ARGV[2]:生存时间
ARGV[3]:getLockName(threadId),这个线程对应的锁名称
释放锁的逻辑为:
广播0表示资源可用,即通知那些等待获取锁的线程现在可以获得锁了。