00:00
好,前面呢,我们给容器中配置了一个red client,那接下来我们就测试使用它来完成我们分布式锁功能,当然所有东西我们就来参照reddi的官方文档,Reddi功能有很多,比如我们可以做分布式对象、分布式集合、分布式锁等等,那么接下来就来测试我们的分布式锁,来点进来。首先我们来测试,第一个我们叫可重入锁,那什么叫可重入锁,如果大家以前系统的学过guc里边的一些锁,包括一些相关概念,那这就跟它是一模一样的,如果没有学习过呢,大家可以参照周阳老师讲的GC,或者跟着我来直接用一下就行。好那我们简单解释一下,可重入锁它呢,其实指的就是如果我们现在有一个方法A,方法A呢,我们在里边调用了一个方法B,然后呢,我们这两个都要加锁,而且A加的是一把锁,比如我们这个锁的标志我们就是1A加的是一号锁,B呢,我们同时也想加这个一号锁,如果是可重入的就应该是这样们A方法好,一进来我们加了锁了,相当于A方法执行了它掉里边的B方法,B方法相当于也要跟A加同一把锁,B一看呢,A已经加了这把锁了,那我就直接拿来用。
01:18
那相当于B方法呢,我们就可以直接执行,执行完了以后A释放锁就行了,这叫可重入锁,那如果设计为不可重入锁,那就糟糕了,我们A呢,假设要加的锁是一锁一号锁,然后呢,B也想要加一号锁,而且A里边调用B,我们一进来,好,A把一号锁持有了,然后他在里边调用BB呢,相当于要等待A来释放一号锁,它才能抢到一号锁,这就是我们说的不可重入锁的设计,但这种呢,我们就发现这有问题了,这就肯定是个死锁了,B永远等不到A区来释放,因为A还想等B执行完了才释放的,所以一句话,我们所有的锁都应该设计为可重入锁,避免死锁问题。所以我们接下来就来测试我们第一个最简单的锁,我们使用reddi给我们默认获取的分布式锁,它就是一个可重物锁的。
02:10
我们嵌套调用,如果是同一把锁就直接使用了,而且呢,Reddison最厉害的是它呢设计的时候,我们reddison里边的这些锁直接实现了我们Java提供的lock接口,这lock接口呢就是GUUC包下的,来给大家看一下,如果大家用过,那这个lock接口,这guc保一下,Lock接口呢给我们提供了很多方法,比如lock方法,这就是加锁,而且呢我们还可以解锁,比如我们下边lock方法呢,还有一个叫unlo方法,我们解锁,这都是C里边的一些内容,我们reddison实现了这些锁全部实现了guc相关的接口,那如果大家以前用过GUUC的锁,相当于可以无缝的转换使用reddi,但无需学习任何新的API,直接以前本地锁是怎么用的,我们JUC里边的本地锁怎么用,那我们用reddi的分布式锁就怎么用,但是呢,我们reddi分布式锁比JC更强大的就是JC是本地锁,它。
03:10
只能锁我们当前服务当前进程,而我们reddi是分布式锁,能锁住我们所有服务。好,那么接下来就来测试这个锁,那有了Di,想要获取锁非常简单,那只需要调用reddi的get lock方法传入一个名字,这个名就是锁的名字,只要名字一样。那就是同一个锁,我们拿到这个锁以后呢,我们调用loook方法,那就是加锁,加锁成功以后我们来执行业务代码,执行完了以后,我们来调用onlook方法,那就是解锁,好,我们把这个简单代码给大家测试一下,我们来到我们的这一块,那为了测试简单,我们直接来到web里边,我们就以这个hello请求为例,我们来测试,我们给它进行加锁,执行好,我们要执行,我们直接注入red client,我们给容器中之前刚放的来写一个owa,而且呢,我们注入的时候,这个变量名可以写成我们当时方法的返回名,我们叫reddi,这其实就是这个组件的ID。
04:11
我们写成它,那就更好不过了,好,我们来写到这儿,写成red client也肯定没啥问题,好来直接复制过来,我们拿一个red,这相当于red的客户端,我们直接点一个调用它一个方法叫get look,这就能获取一把锁,锁的名字随便写,只要名字相同,它们就是同一把锁。好比如这个名字呢,我们就叫my look。我们来写了这一个,好,我们来返回,我们返回了一个rlook,这个RL呢,我们来点进来看一下,它呢,其实是我们这个red包下的,诶这个包下的它也是一个接口,但这个接口呢,继承了lock接口,我们来看这个lock接口是C包下的,比如我们这个red的锁,直接实现了我们JUC锁的这个接口,所以JUC怎么用,我们这还怎么用好,这是我们第一步,第一步我们在调用get lock我们获取。
05:04
一把锁,而且呢,只要锁名一样,只要锁的名字一样,那就是同一把锁,所有人只要用了同一把锁,那就把它们都能锁住,就是同一把锁好,然后呢,这是我们获取到锁,接下来我们第二步,那想要加锁,我们就要执行我们加锁代码,我们必须拿这个获取到的锁,手动的调用它的look方法,我们来进行加锁,只要我们这个方法调完了,我们就会加锁成功,那么就在下边去来执行业务代码,比如我们来C4OUT来输出一句话,我们就叫加锁成功,执行业务好。而且执行完了呢,我们还要解锁,比如我们来调用lock,它还有一个方法叫unlock,就是我们使用它呢,我们手工的进行加锁解锁,而且我们说为了保险起见,我们代码应该这么来写,我们业务代码呢,可能会出现问题,好我把这个业务代码放在这儿,无论出不出现问题,我们最终都要进行解锁操作,好这是我们说的这个加锁解锁代码,我们只需要手动的调用加锁,然后呢调用解锁,我们整个锁就完成了,我们在调用解锁。
06:19
那我们来测试一下,为了我们这个明显期间来模拟业务的超长执行,比如我们来red sleep,让他睡上30秒,们在这儿来catch一下,异常好catch,我们写一个EXCEPTION1,那业务呢,睡上30秒,模拟业务执行很长时间,执行完了以后,我们在这儿来进行解锁,们来输出一下控制台输出,我们叫释放锁,包括呢,是哪个解的,哪个加的,哪个释放的,我们都把当前的线程号来打印一下,好点一个current。Thread点一个get ID,我们在解锁的这一块,我们也打印一下这个线程号,好,我们现在就来启动我们这个服务,来启动服务,我们来测试一下我们商品服务,我们现在来把商品服务启动起来,我们来看一下我们现在的这个分布式锁能不能使用,我们来多发上两个请求,那我们发第一个hello请求,他在这儿来获取锁,他获取到了执行,当我们再来发第二个hello请求,他想要获取锁的时候,别人还业务正在执行,没解锁呢,所以他就得等。所以我们说的这个lock代码这个东西大家一定要注意,它是一个阻塞,是等待,我们说阻塞是等待,什么意思呢?我们以前没获取到锁,我们还想获取,我们是这么来写的,比如我们在from DB,我们在这获取锁,我们在这儿占坑,如果占到了,我们执行业务没占到,我们自己把自己再调用一遍,再来占,我们相当于写的是一个循环,这是我们的自旋方式。但是我。
07:52
我们说在reddi的时候,我们只需要调一个lock.lo如果加不到锁,它就会在这一直等,直到他能拿到锁,他就会运行下边的方法,所以呢,这是一个阻塞式等待,我们只要能执行到下边肯定就是拿到锁了,我无需写我们以前的那一堆代码,我们来看一下我们的效果,我把控制台清空,我们来发local host 1万端口hello请求呢,我们来多发上两次,这是1万端口们来找到hello请求,好,这是一次,我们来再来发一次,那现在呢,两次hello请求,大家看控制台,然后第一个hello请求,他说加锁成功执行业务79,这相当于线程号,然后呢,它在执行,我们来模拟超长时间,而且我们来看我们的第二个hello一直在转,因为第一个不执行完不释放锁,我们第二个没办法,所以我们就在这儿看,我们等待我们第一。
08:43
一个业务执行完,我们将所释放来,稍等一下。好,我们发现释放锁了,只要他一释放锁,我们立马打印了81号线程又加锁成功了,他又在这儿来执行,所以呢,我们二号加锁成功了,相当于模拟业务超长,它一会儿又返回,但一号呢已经返回了,这就是我们分布式锁确实锁住了,而且加的锁长什么样子,我们也可以看一下red里边是怎么存的。
09:10
大家我们看现在,现在两个都返回了,现在里边肯定没锁了,我随便再发一个请求,好,它获取到锁,那肯定在red里边占坑了,我们来看一下,占了这个坑位就叫my look,诶我们自己写的,而且然后呢,他在这加锁的时候,相当于也自己整了一个UID,还冒号82,我们来看一下这个冒号82,诶这当前正是我们的线程号,相当于我们自己线程加的锁,那最终呢,它解锁也是我们自己线程来解锁就行了,那么只要执行完,它会自动的去来解锁我们red里边的锁,好它这个锁只要一释放,我们来重新刷新一下,发现red里边就没有锁了。所以他的这个枷锁呢,其实跟我们之前的加锁代码是一样的,但是呢,它有一个更优秀的地方来,我们来考虑一件事,大家想啊,那在这儿手动的调用加锁和解锁代码,那如果由于我们运行出现问题,我们代码走到这儿,程序闪断了,我们停机了,那我们的解锁代码没运行,Rabbit会不会出现死锁呢?好,来假设假设我们这个。
10:19
解锁代码没有运行们的dison会不会出现死?那么就来测试一下,怎么测试呢?我来启动上两个,我们这是1万端口,还有一个10001端口,我现在把这个启动两个,我们1万端口呢,相当于我们来发送一个hello请求,10001也发送一个hello请求,但是呢,谁把锁抢到了,它在执行期间我们把它程序给他停掉,那相当于我们没有执行释放锁操作,我们来看别人能不能获取到锁。好,来看一下效果,现在我们的里边没有占这个坑,好接下来我们把这两个控制台我们全部清空,全部清空,全部清空呢,我们要运行上两个hello,请求一个是给1万端口来发,一个呢是给10001端口来发,好我们来看现在这两个呢,我们全部都清空掉,我们先来给1万端口发,我们再来复制一个给一万零一发。
11:23
但是大家现在注意好,两个人要强锁,我们先让1万端口执行,然后呢再让10001执行,当然1万端口大家来看,现在呢,1万端口加锁成功了,当然业务还没释放锁,我来把1万端口停掉,我来模拟停掉,然后呢,它没有运行解锁代码来看10001 10001呢,我们在这儿转,那我们来等到什么时候它能运行成功呢?我让它一直转,我们来看我们的这一块的锁,我来刷新,来我们这个锁啊,确实没得解,我们来稍等一会儿,我们看他能不能自已把锁抢占成功。如果不能抢占成功,那说明有死锁问题,如果能抢占成功,说明没有死锁问题,我们这个1万端口呢,已经是停掉的,我们来看这个10001,好,我等了大概30秒钟,我发现这个10001现在也正常返回了,我再来刷新,包括我们来看控制台,10001的控制台,我发现他呢打印这个加锁成功79了。
12:24
而且呢,我们再次刷新新的线程,已经进来加锁了,我发现没有思索,问题就是即使我们手动没有解锁,它也会为我们解锁,是什么原因呢?是由于我们加锁的时候有一个默认超时时间吗?我们再来运行一下啊,10001我来刷新一下,来看一下他加的这个锁,来刷新那这个锁。我发现这个锁呢,有一个TTL26,好,我们来再来不断刷新,我发现这个锁我们不断刷新的时候,我们发现这个啊,由于二十六二十三我们来再等一会儿,诶,我们发现又变成30,我发现这个存活时间它是不断的在这变,都已经退到18了,它又变到30了。
13:06
这是一个什么?这是我们说的reddi里边有一个叫看门狗,他能对锁进行一个自动续期,就是我们业务在超长执行期间,我们发现它马上就要过时了,但是啪又续了满时间,比如我们30秒这个GTL的时间单位是秒,所以我们说这就是reddi的强大之处,两点,第1.red解决了两个问题,第一个锁的自动续期,锁的自动续期。就是呢,如果我们在这模拟业务超长,我们在这等的时候,如果业务超长,如果业务超长它呢,会在运行期间自动给锁续上新的,咱们这个30秒这个周期,所以呢,我们先不担心,第一件事情就是我们业务执行期间,他自己的锁过期,把锁给删了,所以呢,我们不用担心,不用担心。
14:07
业务时间长。锁自动过期被删掉,因为我们在这没指定锁的时间,但是我们也发现了,我们即使没指定锁的时间,我red里边默认保存了锁的时间,那默认上来加的锁都是30秒这一块呢,默认加的加的咱们这个锁都是30秒,30秒时间。如果时间不够,会在业务期间进行自动续期,所以我们会发现,即使我们不手动解锁,那也会等很长一段时间以后,有这个30秒,只要我们看门狗没给他继续续期,我们业务只要运行完了,没人给他自动续期了,30秒以后呢,这个锁也会被删掉,好,所以接下来就是我们说的第二个。也就是说,加锁的加锁的业务只要运行完成,运行完成就不会给当前锁续期,当前所续期即使不手动解锁。
15:17
所呢也会默认在30秒以后我们自动删除,所以呢,我们这个red啊,不会有死锁问题,而且还帮我们加了锁的自动续期,而且我们说这个lock是一个阻塞式,等待我们发现我们第二个hello请求,只要第一个不释放,它就一直在这转圈圈,他得到锁以后,他才会执行超长业务,最终返回完成,只要没有得到,他就会一直在这儿重拾得锁。包括我们来看它的语言代码,我们来点开来看这个red look里边,他想要加锁,就会调用这个look方法,我们来点进来,这个look方法呢,在这儿执行一堆,别的我们不看,我来给大家看一个东西叫不要true。
16:03
也就是他利用一个死循环,不断尝试获取锁,不断尝试获取锁,只要能获取到了,那我们就可以来继续执行我们的业务,获取不到它就是不断的尝试,所以这是我们red里边最简单的这个锁的体会,调用lock方法。
我来说两句