友情提示:看该文章前,需要对Redis的有基础的认识:redis的数据结构和运用。不懂别着急,一点点来,先去看redis的基本知识。
Redisson是什么?
Redisson是基于redis服务的一种分布式锁,其功能强大,其适配各种模式下的Redis集群。
那有同学会问:redis的集群模式有哪几种呢?集群的特点又是怎么样的?
redis集群有那几种呢?
常见的有4种,分别是:
- 单机模式(Single)
- 主从模式(MasterSlave)
- 哨兵模式(Sentinel)
- Cluster模式
这都是概念上的介绍?那各自有啥特点呢? 但本文不是介绍与集群的模式特点和各种的优势为目的,但可以参考一下文章: juejin.cn/post/684490…
Redssion加载Redis集群不同模式配置
那有同学又问,Redisson是怎么适配Redis集群的呢?漂亮就需要这种探究的精神,我们都是努力成为最强程序员的男人。 可以通过Redisson的代码,可以看到其是怎么整好Redis的集群模式的,代码如下:
其Redisson客户端提供了相应的链接管理其,其通过配置模式加载对应的Redis集群下的配置信息。并创建Redis的链接请求信息。分析一下ConnectionManager其是一个接口,那他的之类结构是怎样的?
分析发现这就是Redis的集群的不同链接请求管理器实现,他对4总集群模式的链接都进行适配(策略模式),Replicated是什么东西啊(我也不知道,点击看看呗~~,其实AWS Azure和Azure Redis Cache的一种适配,其会拿到其复制组集群,进行遍历个节点的信息,从而判断其是否发生角色转变,获取最新的主节点,类似于充当自身的哨兵)
Redisson如何实现分布式锁?
我们先不探究如何是怎么实现的,我们先二话不说先搞起来用用再说,好东西用了才知道。
public void run(){
RLock rLock = redissonClient.getLock("anyLock1");//非公平锁,默认
try{
rLock.lock();
System.out.println("我获取到锁成功了,开始执行并发操作");
}finally {
//释放锁操作
rLock.unlock();
}
}
他运用起来有点像ReentrantLock啊,先获取锁后,需要手动释放掉锁操作,用法都一样,那还有什么能难倒我们这些聪明“绝顶”的程序员的啊!
Redisson的Lock代码实现的分析
代码分析部分比较枯燥,而且让人想睡觉
但为了变优秀,为了变成熟,为了追到姐姐,为了成为她人的依靠,枯燥的东西都会变得有趣。 代码跟进发现Redisson继承的是jdk.Lock接口,看吧~~,继承JDK.Lock爸爸准没错。
接下来,我们看看其Lock的代码的实现吧
我们发现其调用的是Redisson.lock(leaseTime,timeUnit,interruptibly)方法. 该方法的参数的解释:
leaseTime:表示持锁时间,默认-1表示不指定redis的锁的超时时间
timeUnit:时间单位
interruptibly:是否响应中断\
继续跟进,看看tryAcquire(-1,leaeTime,unit,threadId)方法(这怎么这么像我自前些的tryLock方法呢??),没错看字面意思就是:尝试获取锁操作。
跟进后,发现其内部就是一个异步获取的方式,涉及到异步编程的东西。我吓的擦了擦汗水~~~ 其内部对判断其锁有没有指定超时时间,如果走不通的获取锁逻辑,我们先分析其指定了获取锁超时时间的代码(leaseTime!=-1)。
进入方法发现:这是什么脚本语言啊? 这是lua脚本,其内部就是执行调用redis的一些指令方法。 哪为啥要用lua脚本编写这些呢? 这跟lua脚本的特点有关,lua脚本可以保证,代码指令的原子性。同一时间全部执行执行并返回处理逻辑。其实脚本中的代码就是redis指令而已,看懂是莫问题的~~~
加锁逻辑的实现lua脚本分析
其实Redisson的加锁逻辑的核心就在于这段lua脚本中,我们细心分析这段lua脚本。
if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"return redis.call('pttl', KEYS[1]);
lua脚本参数分析
来来来,先不看代码,先分析一下参数。
keys[1]:Rawname,什么是rawName??其实就是我上面写的运用代码的anyLock1这个值
ARGV[1]:就是leaseTime,锁的持锁超时时间
AGRV[2]:这个就有点东西了,锁名称?这个是怎么生成的?\
锁名称是如何生产的呢?
对呀,就会好奇这个锁名称是怎么生成的呢? 我们追代码进行看看
其用本地线程threadId和什么id进行拼接的?\
此时的我也是不知道,有点懵逼?但还是看代码吧
这时发现跟链接配置有关系,走她,接着进去。发现ConnectionManger是个接口,那我就随便找一个实现发现,所有的最底层的子实现都继承了MasterSlaveConnectionManager
发现这个时候,id就开始被指定进去了而且是UUID而已。 那么得出来的结论:ARGV[2]=UUID+":"+threadId
为啥要UUID+“:”+threadId作为锁名称
那问题来了,那为啥要这样指定分布式锁的Id呢,为啥不直接用threadId呢? 由于是在分布式的服务中,由于仅仅通过threadId根本不能指定到是哪个服务器节点的那一个线程进行执行,那就必须用一个全局唯一的前缀进行区分了(UUID) 有同学问?那能不能不用UUID呢? 可以啊,只要保证每台服务器节点生产的分布式锁前缀保证区分就行。但这就要每台服务器节点进行维护写前缀了,算了算不划算(没闲情搞这些,但是有这场景的哦,你们自己想想看),那还不如直接用uuid(好用又方便)
开始正式分析lua脚本
if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
"return redis.call('pttl', KEYS[1]);
- 其先判断是否存在anyLock1在redis中,如果不存在?
- 就创建一个anyLock为key的redis的map数据,且为filed为UUID+":"+threadId,赋予值+1操作
- 并且创建成功后,设置其超时时间为leaseTime
- 返回一个null.
这不就是没有值情况,第一次获取到锁线程返回的情况吗? 我的天,你太厉害了,这都猜到了。
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end;
我们接着分析第二段代码
- 假如上一步不成功,其判断anyLock1中存的filed:UUID+":"+threadId是否为子身的值。
- 如果是,我就进行对anyLock1的filed的值进行+1操作
- 并且重新设置持锁的超时时间
- 并返回null,表示持锁成功 还有最后一段lua脚本,快分析完喽,嗨皮一下!!
return redis.call('pttl', KEYS[1]);
- 这不就是判断其持锁的线程,跟自己半毛钱关系都没有,白等了,要不再等等看看你还有多久
- 所以返回pttl,还有多久你才释放锁时间 漂亮这不就分析完了吗?
那未设置锁的超时时间,是怎么实现的?
哪还是回到leaseTime=-1的情况分析一下喽
同学说,这怎么有两个地方涉及到啊?指令异步执行完后,好像也用到判断啊
- 没错,我们一个一个来,在点进去发现,其用了一个internalLockLeaseTime作为超时时间,默认是30000毫秒\
- 处理加锁逻辑还是上面的lua脚本,没有差别
- 哪差别在哪了?
没错,就在第二部分,在异步执行完成后,其会判断执行scheduleExpirationRenewal方法。 哪这个方法到底是在干嘛呢?????? 预知后续如何,请关注我,下周更新,分析其内部是做啥的(悄悄说一声:WatchDog看门狗哦)
总结
Redisson实现分布式锁,其内部不仅仅这些,通过观察和运用,发现其功能之强大,其已经实现了jdk的公平锁于非公平锁、读写锁、红锁、偏向锁等等功能。
功能之强大,是不是勾起你学习的欲望了。
我也向往湛蓝,愿成为你的湛蓝。谢谢