00:00
好,前面呢,我们给大家演示了一下本地锁在我们分布式系统下出现的我们不能正确加锁的问题,那原因就是我们这个本地锁只能锁住我们当前进程,但是当我们分布式情况下,微服务众多,我们服务复制上很多份以后,那每个锁只锁自己的,那最终不能实现我们想要这些所有的服务合起来只能有一个线程放进来执行任务,所以呢,我们在这种情况下,我们就是要使用分布式锁,那分布式锁该如何使用呢?先来考虑一种简单的原理,比如我们现在呢,有这么多的商品服务,现他们都要查数据库,但是我们的约定现只有一个人能查数据库,查了以后呢,放到缓存里边,那这下呢,所有的服务都过来需要抢占一个锁,那如果是本地锁,我们可以使使用一些语法。Snchized加一个this,特别是大家注意这个this呢,就是我们这个锁对象,大家只要用的是一个对象,那说明用的是一把锁,那就能锁住了,那在分布式情况下也一样,我们这个this呢,肯定在分布式下没得用,但是我们可以考虑现实生活中的一个例子,比如我们几千个人都来去上厕所,那么这个厕所呢,假设就一个坑位,那我们如何保证他们有序的进行,那就是大家上来,只要我在这儿占住了这个坑位,那别人呢就不能进来了。所以我们分布式锁呢,也就是这个思想原理,那么所有的商品服务,无论是你是哪个服务,大家都去一个公共的地方占所,也就是我们说的占坑,如果我们占到了这个坑,我们就去执行业务,那没占到坑的人可以来等待一下,等我们业务执行完了,我们想要释放锁,那释放完了以后呢,别人就可以继续来占这个坑了,我们说的这个占坑啊,我们可以去任何一个地方占。
01:51
只要大家都去公共的一个地方,比如大家都去数据库,哪怕给数据库里边插入一条记录,我们都插一号记录,如果呢,这个人插入进去了,那说明是成功的,那这个人呢,如果插入的时候一看已经有一句话记录了,那说明他不能插入成功,说明别人给这儿放了一把锁,占了一个坑,我们可以去数据库,当然也可以去redis,数据库太慢了,我们直接去red。
02:16
当然呢,全去red占一个坑位,这个坑位呢很简单,我们只需要用set set就是给red里边保存一个KY,六比如我们呢,我们的K就叫look,只呢随便只要谁能把look set进去,比如我们第一个服务set进去了,那说明它得到锁了,第二个服务set的时候呢,一看,诶,我们redmi里边已经有look这个K了,那说明人家已经占了一个锁了,那我们就不能占。所以我们分布式的基本原理,那就是所有的服务都去共同的一个地方占坑,只要能占到,那说明你拿到了所占不到你就不能执行。那这个占坑呢,我们来考虑,我们参照redis的官方文档,比如我们来打开redis,那为了好看起见,我就打开redis中文官方文档们,来到文档这一块,我们主要来看red的命令里边,Red命令里边呢,就有一个这个占坑命令,来到这个strength的集合里边,我们来看,我们在下边呢,会。
03:16
有一个叫set,我们设置一个KV值,好,我们来点进来,那在这呢,我们就可以使用这个命令给red发送一个命令叫set,比如我们的K就叫lock y6呢就叫一,这样呢,只要能set进去,那就能占到坑,但这个占坑呢也比较有讲究,它后边呢还有几个能带的参数,什么exx,这叫过期时间,我们可以指定多少秒以后,还有我们这个PX,这是毫秒,而且呢,占坑的时候注意我们还可以有两个操作,一个叫NX,一个叫XXNX呢叫not exist,也就说我在占坑的时候,Set我给里边放,放的时候呢,如果我们加了NX,那就是不存在,我才给里边放,相当于如果我们red里边没有lock这个东西,那我就占有了,我就等一下我就不占了,而且我们分不式锁,就是基于我们set带了NX参数这个命令来做的,也就是说呢,我们来模拟。
04:16
假设呢,有多个客户端我们都要抢占一把锁,那么现在呢,所有的客户端我们都联想red他们呢,全部都使用一个命令,叫set NX NX呢就叫not exist,就说我们不存在的时候,我才给你里边放,所以大家全部进来,都来给red发命令,说不存在就放,不存在就放,这个命令呢是一个原子性的假设二号客户端进来,他执行了快,他先执行了NX,那他给里边放了lock,那别人再要进来,由于使用的是set NX命令,比如说判断red里边没有这个东西了,我们就设置,结果呢,我们之前有,那它相当于就设置失败了,只要他。给red里边尝试保存东西失败,我就认为它占锁失败,那么把这个场景呢,给大家模拟一下,比如连上我们的这个虚拟机,好,我先连上,我把这个东西呢,我复制上我们几份,这个我来多复制几份,我来模拟多个客户端,全去redis里边来抢占所操作,那怎么模拟呢?我在下边来输一个命令。
05:21
我们叫docker ex e,我们先来给大家看一下docker p,那现在red呢,正在运行中,我们这个red我们使用docker ex e-I,我们red相当于我们要进入red容器,我们以前呢是B办式进它的控制台,我们现在不了,我们使用red client,好我们相当于呢,让所有的客户端全部就进入它的red命令行程序,好我们把这一块呢,我们来选中,我们让它发送给全部绘画,好然后我们来看,我回车,好,我们现在来看全部绘画呢,全部现在全都进入了我们这个reddi容器里边,而且是命令行的方式,那我么来使用站锁命令,那怎么战锁命令呢。
06:07
我们这个命令是set,我们发现这可以写一个K,比如K我们就叫lock,值就叫一,然后我们可以带一个参数叫NX,那NX说明我们是不存在的时候才放,而且呢,如果我多个客户端同时来执行这个命令,比如set,我有一个叫look,值呢叫哈哈,然后呢,我们来写一个参数叫NX,我们把它呢,全部一定全部发给我们这个客户端,相当于我们这四个客户端全部给red里边发了同一条命令来看谁能站成功回撤,好,发现一号客户端返回了一个闹,他没站成功,二号客户端呢,返回了一个OK,而三号客户端呢,返回了一个档,四号也返回N档,诶,我们发现确实只有一个人会返回OK,这就是我们set NX命令,我们分布式锁的基本原理,我们都尝试去一个地方占坑,能占成功的人就会返回,OK,包括我们来看我们的red里边,如果我们加勒索,我们red里边就会有一个lock。
07:07
这K值是什么我们倒不在乎,就是大家都来用这个K保存一个东西,看能不能保存就行了,这是我们使用set NX命令给red里边存数据,这也是我们分布式锁的基本原理,但是我们这样做有没有什么问题呢?我们来分析一下,好,我们说分布式所阶段一我们是这么来做的,大家想要获取锁呢?好,都去red里边占坑,假设呢,1万个线程全进来,全部呢先来执行,第一步叫获取锁,怎么获取锁呢?这1万个线程全部给red发送set NX命令,比就说red里边没有这个lock的时候我就站,然后这1万个肯定只有一个能占成功,那占成功了我们就获取到锁,我们再去执行业务,执行完了以后呢,我们删除锁,再结束,没占成功,我们可以在这儿等一下,等一会儿呢,我们再来尝试,那编写成代码呢,那就是这个样子,好来看我们原来的这个方法呢,它是使用本。
08:07
闭锁来重新给它复制一个,那现在来使用我们这个red分布式锁的方式,那这个方法我们就命名为with local。Block。我们把这个呢,复制一份,我们把这个方法来改造一个使用分布式锁的方式,好我们这个尾字,比如我们就叫red的这个look,我们使用red来做一个分布式锁,那在这呢就来调用这个方法,以前呢,我们是在这使用synchronize的,我们来进行加锁,我现在把这一块呢全部删掉,包括呢,我把这一块我都来删掉,我们来到这最终查询完了以后,我们把它再放到缓存CTRLL,我们来代码整理,但是呢,Sequence没法用,我们现在得占分布式锁,怎么占用分布式锁,所以我们来的第一步那就是占分布式锁,我们抢占分式锁呢,其实就是去red占坑,这占坑操作呢,就是大家都去给red里边保存一个件相同的东西,我现在呢,假设并发1万都来执行到这个方法好现呢,全部先给red中保存东西,怎么保存呢?我们使用red table options for value,它呢有一个方法叫set,如。
09:20
如果光是set的话,就没有那个功能,在red里边有一个命令叫set NX,我们在这儿刚才试了一下set ns呢,对应的代码是我们这个set if ament,这个就是设置,如果它不存在的话,来点进来,包括我们来看这一块的解释,那么set if ament,那就是我们red set n X,好,那我们现在呢,就来设置一个K,比如K呢,我们就叫look,值呢,我们随便来写111都行,然后呢,我们这个站位完了以后,会返回一个结果,因为如果100万线程都进来,大家都先执行这一句话,这句话呢,执行如果站成功了,我们red this会返回OK,这块直接返回或true或false,所以我们就得判断if look,那这块是true,那就说明成功了,我们就叫加锁成功,哎,我们占道坑位了,否则的话,那就是没成功,那加锁成功了,我们才有必要执行我们这一段的努。
10:20
记好,把这一段呢拿过来。那为了简单起见,我把这一段呢,我们来提取成一个方法,好,我们来提取成一个方法,那在这来指定一个method,我们就叫get data from DB,好,那现在呢,我们加锁成功了,我们才有必要执行我们这个方法,好,我们就把我们这一块来改造一下,这个是本地锁的,我们上边这儿呢,好,那就在这儿加锁成功,我们来执行这个,并且呢返回,因为它有一个返回结果,这就是来执行业务,执行务,那如果加锁失败,Else,这就是加锁失败,但是加锁失败我们也得返回数据啊,加锁失败如果我们直接去查数据库也不合适,所以加锁失败呢,我们就应该是这样子的,我们可以等待上100毫秒以后,我们再来获取一下分布式锁,看看能不能加锁成功,包括呢,我们就算加锁成功,执行业务,我们都是先看缓存中有没有的,所以呢,如果加锁失败。
11:23
我们要重试,这就跟我们使用snchized的那么这个一样,我们这个呢,它这个本地同步锁,它会一直在这监听,只要别人一释放,他就会拿到,所以说它是一个我们称为自旋的方式,他一直呢在这重试,重试,重试,那我们这一块呢,重试也很简单,我想要重试相当于我还要执行战锁流程,怎么着,那就相当我把自个方法再调一遍,好return,我把自个儿呢再来调一遍,这就是我们说的自旋的方式啊,自旋。的方式。然后呢,他在这儿来重试,那重试呢,第二次再进来,他再尝试加锁,只要没加到锁,他就继续重试,但我们嫌重试频率太高了,可以在这儿给他休眠一段时间,这也行,比如我们这个休眠。
12:14
100毫秒,我们这个重试,那这是我们说的加锁代码,当然我们业务执行成功以后,我们还要解锁,锁怎么解呢?我把这个业务代码呢,放在这儿好,我们业务最终要返回这个,那如果我们这个业务执行成功,别人还要能把坑占到,那我们就得把这个锁删了,所以呢,我们使用red tempate,它直接有一个方法叫delete,那刚才我们放了这个key,我们叫lock,我们现在把这个删了,那别人就能占成功了,就像我们这一块,诶当时有人占了一个这个lo锁,现在我们来看,在我们这四个里边呢,他们都占不到锁了,我们现在比如我们再来发一个命令,因为大家呢全把锁都占了,我们就叫set NX lock1我们这个命令呢是set lock,比如我们随便写一个值,我们就叫NX回车会发现呢,这不行,那每一个这呢都不行,因为我们之前已经有锁了,所以我们可以把red里边的这个东西锁,如果我们删了好。
13:14
其实就是这个站位删了,那大家现在重新来抢占set lock,比如我们就叫LOCK11,那们就叫NX回车,好,第一个抢不上,来看第二个,第二个能抢上,总是第二个,第三个抢不上,第四个抢不上,所以呢,我们执行完业务以后,我们一定在这记得我们要删除锁,这就是我们的删除锁,所以我们分布式锁的简单逻辑,我们转换成我们业务代码,那就是这么一段代码,但就是这一段代码又会不会有出现什么问题呢?我们来说这一块的问题还是挺大的,我们来考虑一件事情,比如我们在这调用代码set if z,就是set NX,我们现在占到坑了,我们相当于获取到锁了,获取到锁以后呢,接下来我们来执行业务,好在这儿来执行业务,执行完了以后呢,我们来删除锁,删除完以后结束没获取到锁呢,我们这儿也有逻辑,没获取到以后呢,我们可以在这等待一会。
14:14
自个儿把自个儿再调一遍,再进来重新获取锁,但是我们来考虑一个问题,如果我们这个业务代码在这执行期间出现了异常,导致我们程序抛异常直接退出了,那们都没有执行删锁代码,那会出现什么现象?就像我们之前的red里边lock没删,别人想要再来占用,就没有这个坑位了,所以这就导致了我们说的死锁问题,特别是大家说害怕出现异常,那我TRY看放到final里边行,你放到final里边,那再来考虑另外一种情况,我们这个代码呢,不是由于异常崩了,而是我们代码正好执行到这儿,准备来执行删锁的时候,我们机器断电了,程序在这直接闪断了,那么没有执行了删锁这段代码,那相当于red给我们没有删除锁,那别人还想要占这个lock,那就占不到了,所以这就是我们说的第一个最严重的问题,如果我们解锁失败,就会导致死锁,那解决这个问题的办法是什么?
15:14
我们可以给锁设置一个自动过期时间,比如我们在这儿占锁的时候,我们来给他设置一个过期时间,比如30秒,那就是30秒后,即使我们这一块业务崩了,我们自己没有删除锁,RA呢也会把这个锁自动删除,所以呢,我们应该进入到阶段二,我们现在获取到锁以后,不能直接来执行业务了,而应该先给他设置一个过期时间,比如我们在这儿,好在这一块呢,这是来加锁成功了,那加锁成功上来就我们来第二步设置过期时间,要不然我们来不及删锁,那就死锁了,那就设置过期时间,那这个过期时间的设置呢,我们可以用red timet,它呢有一个方法叫X pair,那这个方法呢,就是给某一个K,好,我们这个K呢,就是这个锁,这个K,我们设置一个过期时间,比如我写一个30 30什么呢,我们比如30秒,我们来指定一个。
16:15
时间单位我们就叫秒,诶,这是分钟来指定成秒,30秒,那30秒以后它会自动筛,即使我们执行业务代码中间我们出现了问题也没关系,我们reds呢,会自动的帮我们删除这个锁,但是如果就这样就完了吗?就完美了吗?我们说还没有,那现在呢,还出现一个更大的问题,就是我们的这段代码。还是我们之前说的话,假如我们刚拿到锁,A判断进来了,我们从这闪断了,我们设置锁的过期时间,代码没执行,那相当于过期时间给他没设置上,那同样就导致了思索,所以我们把这段代码呢继续来解图,它出现的问题呢,就在这儿,我们在前边呢是占锁,然后呢在这是设置过期时间,如果呢,它的这个中间好中间,然后呢出现闪断,那就完蛋了,这块如果突然咱们这个断电。
17:14
那我们这个锁呢,放到这儿,那就成了一辈子的死锁了,所以要解决这个问题,怎么解决,我们说这个问题,其实说白了就是我们刚才看到了我们这个加锁跟我们这个设置过期时间,如果是一个原子操作,就是我给red让它占锁的同时加上过期时间,那只要能返回OK red里边本身就已经设置上了。返回不了,那就是加锁失败,所以呢,我们出现这个问题的原因就是我们在这一块,我们获取锁跟设置过期时间不是原子的,想要原子呢也很简单,我们来看red里边,里边呢还是这个set命令,除了NX参数不存在才设置的时候,我们还有一个参数叫EXEX呢我们能设置过期时间,这个过期时间是个秒,PX呢是个毫秒,所以我们现在我来这么来设置好,我把里边的这个锁呢,我来删一下。
18:13
我现在来使用这个来设置,我们有一个命令,我们来给大家演示一下,叫set,好我写一个look,值呢是111,然后呢,大家看这有一个叫ex,好,我就写个ex,我们写上过期时间,比如我写一个300,那就是300秒,然后再写一个NX,叫不存在的时候才占坑,我们就这一条命令,相当于占坑加过期时间,我们同时都设置好了,好,我们来回车,好我们发现占坑,OK,包括我们来TTL,我们一直可以来观察我们这个lo的还有多久,我们来回车,那现在发现它剩了292,我们再来看剩290,我们一直来看它呢,过期时间就会慢慢的减,所以我们想要占坑啊,我们必须让站位和我们设置过期时间,必须是一个原子命令,只要发给red成了,那就全成了,败了,那就全败了,那我们这个命令呢,我们就不能分两行写了啊,我们说设置过期时间必须和。
19:13
枷锁是同步的来原子的。所以呢,我们就需要用另外一条命令来看set if Amazon,好,我们来看一下set,还有一个F,我们发现呢,设置的时候,我们可以来调用第三个方法,传入时间,传入我们的KV,相当于我们技能战锁,我们传一个K,比如就叫lock,值呢就叫一一,还能传时间,比如我写一个300来时间单位,我写一个秒,比如time unit。我们就叫second,好,这样呢,这一条命令,这就是原子的,包括我们来点进这个命令的实现,我们来看这个命令啊,这块文档呢没说,但是它在底层,底层也是获取一个连接,相当于跟red的连接,直接呢把它的KV以及过期时间全部设置上了,所以呢,这就是我们使用原子命令进行战锁。
20:08
那我们相当于达到了阶段二,我们现在改造成这样,我们占锁的时候使用NXEX2个一起做。那我们就不害怕,我们在获取锁和设置过期时间,如果中间出闪断导致我们死锁的问题,那说白了,我们从阶段一进阶到阶段三也好,我们只做了一件事,就是枷锁要做成原子形的,我们占坑和过期时间被一条命令完成,同样的在山索的时候我们还有非常多的坑,比如我们还是以我们这段代码为例好,我们现在占坑呢,跟我们设置过期时间都是同一条命令,保证原子性了,但是呢,现在我们再来考虑一下我们这一块山锁还有问题,来给大家看一下山锁为什么会有问题呢?来考虑我们这个场景,我们在这业务代码执行成功,我们去来删锁,但是。
21:05
假设我们当时所过期时间我们设的是十秒,由于我们这个业务代码超长,比如我执行了30秒,等我将要去删锁的时候,其实我们之前自己设置的这个锁呀,已经过期了,Red里边已经没有它了,我们想要去删,那你说删一个没有的数据行,这都算好的,最坏的情况是这样子的,我们的业务超时了,我花了30秒,在我执行到第十秒的时候,我的锁已经过期了,Red帮我删到了,而别的前面好多的线程还要抢占锁了,一看,诶,Red里边占不了坑了,然后他在这调用自己的时候在这占坑,你看别人的锁过期了,占不了坑了,他咔,把坑一占,他也在这儿来执行,结果呢,他可能执行到第20秒的时候,他的锁也过期了,诶,第三个人一看,这个坑没人占也占进来,他执行到第30秒的时候,他的锁也过期了,那相当于我们在这。
22:05
但一删锁,有可能我们第二个线程正在用这个锁。因为我们执行到十秒的时候,我们锁已经过期了,第二个线程就已经进来了,我们执行到20秒的时候,第二个线程的锁就过期了,第三个已经进来了,我们执行到30秒的时候,我们执行完了,第三个线程的十秒锁也过去了,第四个都进来了,我在这儿咔一扇,相当于同时把四个人的锁全删了,那家就有更多的人能抢占到锁了,而且我们也能看到,如果我们业务超时的情况下,别人也会拿到这个锁,所以现在呢,会出现两个问题,第一个问题业务超时了怎么办?第二个问题,我们删锁的时候,比如导我们业务超时,导致我们在这儿自己删锁,我们的锁其实早过去了,我们一删,其实删的是谁的锁呢?是别人的锁,所以怎么办能保证我们删锁的时候删的不是别人的锁来看阶段四我们就可以在占锁的时候,我们以前说随便放一个值,我们不随便放了,我们指定一个UUID,这个UUID啊,每个人都不一样,比如我们现在代码。
23:12
把写成这样,以前呢,在这占锁的时候,锁的K叫lock,值呢随便写,现在我来就写一个UU id.random u ID to string,好,我整了一个UUID,那这个UUID呢,比如就叫token。或者我们来写一个就叫UUID,那么这是一个唯一区分标识,好,我把这个UUID放在这,那这样我们想要上来在这删锁,那不能了,因为我们害怕删掉的是别人的锁,所以我们把这个删锁代码我来注掉,应该怎么删呢?我们应该先去查一下red里边点OPS for value,点一个get,我先把我们这个look。占的这个所的值我先拿到,如果拿到的这个if。
24:02
我们拿到的这个look value,就是我们当时锁的时候给它放的这个值,跟我们创建的时候给它里边放的这个值,也就是这个UUID是一样的,点equals是我们这个东西,那说明这是我自己放的,那我就在这儿删除我自己的锁,怎么办呢?我们才可以再来调用这段代码来删除锁,所以呢,这是我们说的深锁的时候,我们得判断是不是我自己的锁,不是了,不能删,但即使在这儿又会出现一个问题。我们还是来模拟我们这个程序闪断,好,假设呢,我业务执行到这儿,我的锁过期时间是十秒,那我业务执行到这儿呢,我也没花十秒,然后接下来我想要删锁,我给redis发一个命令叫把。当前red里面存的这个锁的值拿来,由于这条命令要到远程的red,让red返回数据,我们再接收,相当于我们拿到if的这个判断,中途呢,还有一段时间,假设我们延长就叫一秒,如果我们这个业务呢,执行完刚用了9.5秒,所在十秒过期,大家看啊,我现在给red发了一个获取锁的请求,好,从我现在开始,前面我已经花了9.5秒了。
25:24
我给redis发请求,请求传给redis先走过去,花了0.5,假设我快一点,花了0.3,那正好我到达redis走过去了,Red里边呢,还有这个look look的值呢,就是我放的那个UID,好,Red给我返回,那red正准备给我返回的时候,其实花了9.8秒。我们业务执行了9.5,加上半路用了0.3,我们花了9.8,然后给我们传输的过程中,好路走一半,假如传输呢,需要0.5秒。删,当于路呢,走一半,走到0.2秒路的时候,Red里边的数据过期了,那red咔,把它删了,删了呢,别人啪又占了一个锁,别人呢也叫look,它的值呢是另外一个UUID,所以呢,给我们传回来的值剩了0.3秒,给我们把值传回来,我们在这判断虽然是我们的值,但此时red已经更新成别人的了,所以大家注意这个逻辑,就是这一次的网络交互很费时,就这个费时,期间我业务的9.5传过去花了0.3,当时的时候锁没过期,我们存的值是一,我给你也返回的是一。
26:40
回到一半的时候,我的一过期了,别人抢来变成二了,但来到我们程序里边,我确定是一,也是我当时放的一,然后我咔删了,其实删的是别人的二,所以这就是我们山锁的问题。那山锁为什么又会出现我们这个问题,删到别人的锁,一旦删掉别人的锁,那相当于别的县城又能进来抢占锁了,那相当于与此同时至少都会有两个线程在同时运行这段代码,相当于没锁住。那为什么会出现这个问题?其实我说了这个问题,大家发现跟我们当时枷锁的时候问题是一样的。
27:20
如果我加锁刚加上给我返回成功,我回来我设置过期,结果过期还没设置完,程序闪断了,或者呢,过期命令我发出去了,但是走一半路程序甩断了,导致我们过期时间没设置上,导致我们所一直删不了的死锁。跟我们这个删锁其实都是一样的,原因就是没有保证我们把锁的这个值拿来对比,对比成功了以后我删锁,相当于呢,我们删索两步,先获取值,先获取值对比,然后呢第二步对比成功删除,我们这两步呢,一定要是一个原子操作,所以呢,这两步也得是一个原子操作,不是原子操作就会出现我们分析的那个风险,所以这一块我们想要删锁,我们也得做成原子操作,那这个原子操作怎么做呢?我们就必须结合我们的罗R脚本,包括如果我们去red的官方文档,我们来可以来看一下,来看red官方文档,在red里面呢,他也说了,我们这种设计模式呢,可以用来实现red分布式锁,但是呢,下面这些分布式锁有问题,然后呢,它就优化了一下,我说这个优化我们这个山锁命令想要。
28:39
山锁,我们需要使用一个解锁脚本,就是这个脚本,这个脚本是什么意思呢?我们大概判断一下if if的意思就是。我们来判断red call,如果red调用了获取,调用了相当于get命令,Get命令呢?我们肯定这个K就是look,我们想要获取look的值,如果我们获取的look的值等于我们传过来的这个二值,那我们就给它删掉,否则就返回零,此次结束,因为这是一个脚本,如果我们在这山索能让red执行这一段脚本,这段脚本肯定是要么全成功,要么全失败,Red也不可能执行一半,所以呢?
29:21
我们真正的删锁就要使用脚本,我把这段脚本呢,复制过来,就是我们刚才看red官方文档的脚本,我们把这一块呢,我们去掉,我们必须呢原子操作,我们就是撸R脚本解锁。那如何解锁?我们这段脚本就长这样,这直接是从redis官方文档复制过来的,然后大家注意这有两种变量,第一个叫case,我们传的一个K,还有RG,我们传的一个V值,所以我们想要调用这段脚本,该如何调用?我们使用red temple。它呢有一个方法叫XQ,我们来看执行,执行的时候呢,诶我们发现这可以传一个脚本。
30:07
Script,然后呢,传一个K,再传一个可变参数,那我们就来用这个方法execute。然后呢,我们先来传第一个叫red的脚本red script来点进来,这是一个接口,它的时间类有一个叫default red script,好,那我们就要整脚本,那我们就创建一个,你有一个default red script。我们来创建的时候呢,来点进来,我们把脚本呢传进去,我们来看它的这个构造器可以接收传一个脚本,然后呢,包括返回值是什么,注意我们删成功是什么返回值我们来给大家看一下,如果set一个A等于B,我们来delete a删成功呢会返回一,那删不成功,我们A已经删掉了,删不成功返回零,所以呢,我们在这儿来传入一个脚本这一块泛型指的是返回值的类型,那类型呢我来写一个配,然后呢脚本我们来script放入进去,包括呢我们来在构造的时候。
31:11
它也有一个参数,能传我们返回值的类型。好,我们把这个类型呢也写成这样,我们就能接收一个正确的返回值,第2CLASS。好,这是我们这个脚本,然后呢,接下来传入我们的第二个参数exe,第二参数是一个集合类型的,所有的KK呢,我们现在相当于只删除look,那么就可以整一个A点安姿list,我们把这个look这个字符串我们当成咱们这个K传进去,因为它要一个集合,所以我用瑞工具转一下,接下来呢,再传一个值,这个值是什么?就是我的这个UUID。那我这么一传的作用是什么?最终大家看我们传的这个K,它就会在这儿获取出来K1,这是一个数组,获取我们这个当前元素的值,还有我们这个value的值,它就会放在这儿,相当于呢,Red如果获取我们指定的lock的这个K,它的值等于我们指定的这个值,然后我们就调用red的删除方法,把这个K删了。
32:19
否则呢,就返回零。而且如果删除成功,返回的是一,所以return是一,就删除成功,RETURN0就删除失败,所以我们最终这一块,这就是删数,它最终会得到一个零或一,但得到的值我们也无需要了,我们就在这儿呢进行删除锁,这是一个原子删索,所以呢,Red分布式锁核心两处,第一处加锁呢,保证原子性,第二处解锁保证原子性,这样呢,我们才能锁住我们所有的服务,那说白了就是文档中给我们的两句话加锁呢,使用文档提供的set n X ex命令解锁呢,文档也说了,我们设置的时候呢,我们保存数据的时候不要写固定字符串,给一个随机大字符串,像我们UUID,我们称为token,而且呢,使用脚本来进行解锁。
33:14
加锁使用set n X exs解锁呢?使用脚本解锁,用的分式锁就做好了,但是呢,更难的问题在哪?在锁的自动续期假设我们来想象一下,我们业务时间超长,我们业务还没执行完,锁给过期了怎么办?所以我们就要在执行业务期间,我们要给锁自动续期。当然最简单的解决方案就是我把这个锁时间我放长一点,比如我们这个300秒,我们哪个业务也不可能让它执行300秒这么长时间,我们不会等它,所以呢,我们就让业务去来正常执行,执行完了以后再去删锁,所以我们这一块的代码我们来就来简化成这样来串,来尝试我们的业务执行。无论业务执行是崩溃还是怎么呢,我们都是finally,我们最终呢,全部给他直接进行解锁就行了,好,我们把这个拿过来。
34:10
我们也不关心他的业务异常,把要返回的呢,我们放到上边。那最终呢,只要保证原子加锁,原子解锁续期不想做了,把锁时间放长一点就没什么问题,那这就是我们进行一个简单的咱们这个ready分布式锁,那么来就来在分布式情况下来测试一下。我们在这来打印c out这块呢是加锁成功,这一块是加锁不成功,我们在这来打印,我们叫获取分布式锁成功。那么在这呢,执行业务逻辑,那下边呢,就是获取分布式锁不成功,来等待重试,获取分布式锁失败,我们来等待重试,这个重试呢,我们可以给它等上几百毫秒的时间,比如thread sleep,大家可以在这儿来写一下,那我就不来写这个等待了,那就是没获取到直接。
35:12
继续来重试,无限次重试,我们来现在来测试一下我们整个分布式锁能不能锁住所有的服务,我们现在呢,是来调用这个好缓存不命中,我来查数据库,查数据库开始呢,我们就来使用分布式锁,先来占坑,占到坑以后我们来执行业务逻辑,没占到坑我们就来继续占,来现在来重新启动,还是一样把我们这个代码呢,我们以前复制了很多份,把这很多份呢,同时再来启动。好来启动起来,包括我们把这个代码呢,我们也来重启一下,那现在来保证我们这个里边没有锁,好来刷新一下,好现在这里边呢没有什么锁,然后呢,我们就来测我们的分布式锁,我把这一块清除掉,把这个呢也来重启一下。先让他stop。Return。
36:00
好,我们现在来进行压力测试。我们看一下我们在分布式锁的情况下,我们这个获取数据库到底能打印几遍,把所有的控制台呢,我们来都来清空,都来清空,以前呢是每一个服务都会有一遍打印,那现在我们加入这段逻辑以后,我们来看我们获取数据库会打印几次,好,那现在来启动起来。还是测试我们这个古丽麦尔,这个杰森,我们现在呢,给他的请求还是我们的500个,测完以后我们来看这块呢,有汇总报告,这报告里边呢,有一些异常,来我们来看一下控制台异常,这异常呢都是好等待重试,那这块异常我们来看,这块异常呢,都是我们自己写的有问题,我们在这儿一直是在这儿循环调用,无限次调用,这个占空间溢出了,我们别掉的这么快了,我们还是给它在这来睡一下。好,我们来写一个sleep thread.sleep我们来给它睡上一个200毫秒,好,我们就叫200,把这个异常呢串我们尝试一段时间以后呢,我们来继续重试,好,我们来尝试等上我们200毫秒catch这个。
37:13
然后呢,再来自己调用自己来重新把这些服务全部再来启动起来。我们要保证我们现在呢,只能有一个查询数据库,我们把red里边的这个锁来看一下走。而且锁用完以后呢,他自己也删了,包括我们把这个缓存数据我也删了,因为没有缓存的时候,它才会查数据库,才会强锁好,现在呢,我们把这些结果都来清空,我们把控制台这一块呢,我们也都来清空。把这一块我们全部都来清空,来看他们都各自打印了多少遍,我们这个所的战友,以及我们这个数据库查询的这个信息,我们来进行一个压力测试。好,先来保证我们这一块都清空,把这一块都清空干净。
38:08
好,我们来开始压力测试,还是500个请求,好,我们来运行。现在呢,我们来压测,我们来看汇总报告。汇总报告里边呢,也有一些异常。这异常就是我们不断重试的这个占空间业务,来给大家可以停一个看一下,来看一下这一块直接在这儿调好,我们这个占空间还没溢出,只是呢,我们说这个浪转成应配者不能转,那就是我们在这我们这个脚本返回的呢,不是一个应配类型的,是一个浪类型的,我们就来写成一个浪浪。好,我们来写一个了,重新启动我们这些服务,我们来重新来进行压力测试,把这一块呢来清空。那先把这个控制台来清空一下,好,商品服务我们都把它清空掉。我们来看一下他们这个会打印我们这个多少次获取数据库,好,我们来重新来运行走。
39:00
我们来看汇总报告,保证要没有异常,好现在没有异常,然后我们这个吞吐量100多,我们运行完了,来看每一个后台走。由于我们之前压力测试过,肯定每一个后台这肯定都是缓存命中直接返回,诶我们这个做事之前一定把这个缓存先清掉,来到缓存这一块,好我们把这个缓存呢先来删掉,把这一块我们的缓存数据删掉,然后我们再来进行压力测试,好我们来直接运行。好,压力测试完成,我们也没有什么异常,来看我们的这一块,这一块我们发现都是获取分布式所失败,等待重试,等待重试我们就主要来看数据库呢,它会查几遍,如果我们分布式所获取成功,我们再来调用业务逻辑,这个get data from DB就会打印查询了数据库CTRLC。来看现在有四个服务,CTRLF,我来搜一下,好,1003服务没有,来看1002CTRLV,来1002查了数据库,来看1001CTRLV 1001没有,还有我们这个1万CTRLV,诶没有,我们发现呢,这四个服务我们都锁住了,只有一个查询了数据库,就是我们这个1002那分布式锁的最终效果就结束了,两个核心。
40:22
一个就是原子加锁,在这set n X ex,一个就是原子解锁,使用罗瓦脚本,当然我们发现我们终日锁呢,每次加锁解锁都是这段代码,每次这么来写太麻烦了,大家去可以封装工具类,但是呢,分布式锁有更专业的框架,那我们下一课就来学我们这个封布式锁解决方案更专业的这个框架。
我来说两句