当前位置: 首页 > 科技观察

Redisson分布式锁源码公平锁锁

时间:2023-03-12 03:44:53 科技观察

前言默认的锁逻辑是不公平的。当锁失败时,线程会进入while循环,不断尝试获取锁。这时候,多个线程进行竞争。也就是说,谁抢到了,谁就是他的。Redisson提供了一种公平锁机制,使用方法如下:RLockfairLock=redisson.getFairLock("anyLock");//最常用的使用方法是fairLock.lock();我们来看看公平锁是如何实现的?1公平锁相信朋友看了前面的文章已经熟门熟路了,直接定位源码方法:RedissonFairLock#tryLockInnerAsync。好家伙,这一大块代码我都截图不完了,直接分析lua脚本吧。PS:虽然我们不懂lua,但是ifelse那一堆我们大概能看懂。因为debug发现command==RedisCommands.EVAL_LONG,所以直接看下面的部分。好长啊,都叫了好几次了,好家伙!我们先来看看参数。KEYS[1]:锁的名称,anyLock;KEYS[2]:加锁等待队列,redisson_lock_queue:{anyLock};KEYS[3]]:等待队列中线程锁时间的集合,redisson_lock_timeout:{anyLock},按照锁的时间戳存放在集合中;ARGV[1]:锁超时时间30000;ARGV[2]:UUID:ThreadId组合a3da2c83-b084-425c-a70f-5d9a08b37f31:1;ARGV[3]:线程等待时间默认300000;ARGV[4]:currentTime当前时间戳。锁定的队列和集合是用大括号括起来的字符串。{XXXX}表示这个key只用XXXX来计算slot的位置。2Lua脚本分析上面的lua脚本分为几个部分,我们从不同的角度来看一下上面代码的执行情况。第一次锁的第一部分(Thread1),因为是第一次锁,所以等待队列为空,直接跳出循环。这部分执行到此结束。第二部分:当锁不存在,等待队列为空或者队头为当前线程,且两个条件都满足时,进入内部逻辑;从等待队列和超时集合中删除当前线程,此时等待队列和超时集合都是空的,不需要任何操作;减少队列中所有等待线程的超时时间,无需任何操作;锁定并设置超时。这里执行完返回。所以接下来的几部分暂时不看。相当于下面两条命令(整个lua脚本是原子的!):>hsetanyLocka3da2c83-b084-425c-a70f-5d9a08b37f31:11>pexpireanyLock30000Thread2lock当Thread1被加锁时,Thread2此时会加锁。Thread2可以是这个实例的另一个线程,也可以是另一个实例的线程。第一部分虽然锁被Thread1占用了,但是等待队列是空的,直接跳出循环。第二部分,锁存在,直接跳过。第三部分,线程是否持有锁,直接跳过。第四部分,线程是否在等待队列中,Thread2会加锁,如果不在,直接跳过。Thread2最后会来这里:从线程等待队列中获取最后一个线程redisson_lock_queue:{anyLock};因为等待队列为空,直接获取当前锁的剩余时间ttlanyLock;组装超时timeout=ttl+300000+currentTimestamp,这个300000是默认的60000*5;使用zadd将Thread2放入等待线程的有序集合中,然后使用rpush将Thread2放入等待队列。zaddKEYS[3]timeoutARGV[2]这里zadd命令用来放置,redisson_lock_timeout:{anyLock},超时时间戳(1624612689520),线程(UUID2:Thread2)。其中,超时时间戳作为一个分数,用于有序集合中的排序,表示加锁的先后顺序。Thread3锁Thread1持有锁,Thread2在等待,此时线程3来了。获取firstThreadId2,此时队列中有线程,为UUID2:Thread2。判断firstThreadId2(超时时间戳)的分值是否小于当前时间戳:如果小于等于,说明已经超时,移除firstThreadId2;大于则进入后续判断。第2、3、4部分不符合条件。Thread3最后也会来这里:从线程等待队列中获取最后一个线程redisson_lock_queue:{anyLock};如果最后一个线程存在并且不是自己,那么ttl=lastThreadIdtimeouttimestamp-当前时间戳,也就是看最后一个线程是否还超时了多久;assemblytimeouttimeout=ttl+300000+当前时间戳,这个300000就是默认的60000*5,最后一个线程的超时时间加上300000和当前时间戳,就是Thread3的超时时间戳。使用zadd将Thread3放入等待线程的有序集合中,然后使用rpush将Thread3放入等待队列。3小结本文主要总结一下公平锁的加锁逻辑。这里涉及到很多Redis的操作。简单总结一下:Redis的Hash数据结构:存储当前的锁,Redis的Key就是锁,Hash的字段就是加锁线程,Hash的值就是Redis的重入次数;RedisList数据结构:充当线程等待队列,新的等待线程会使用rpush命令放在队列右侧;Redissortedsetorderedset数据结构:存储等待线程的顺序,scorescore用来作为等待线程的超时时间戳。需要理解的是,这里会增加一个额外的等待队列和有序集合。本文转载自微信公众号“程序员小航”,可通过以下二维码关注。转载本文请联系程序员小航公众号。