玉林社区   吃喝玩乐购   说说从Curator现分布式锁的源码再到羊群效
返回列表
查看: 489|回复: 0

说说从Curator现分布式锁的源码再到羊群效

[复制链接]

1299

主题

1299

帖子

5011

积分

论坛元老

Rank: 8Rank: 8

积分
5011
发表于 2022-2-23 20:02:12 | 显示全部楼层 |阅读模式

马上注册玉林红豆网会员,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

C是一款由J编写的,操作Z的客户端工具,在其内部封装了分布式锁、选举等高级功能。[url=http:///www.wangsu.com/]cdn[/url]的相关资讯可以到我们网站了解一下,从专业角度出发为您解答相关问题,给您优质的服务![align=center]

                               
登录/注册后可看大图
[/align]
一、前言
C是一款由J编写的,操作Z的客户端工具,在其内部封装了分布式锁、选举等高级功能。


今天主要是分析其现分布式锁的主要原理,有关分布式锁的一些介绍或其他现,有兴趣的同学可以翻阅以下文章:


我用了上万字,走了一遍R现分布式锁的坎坷之路,从单机到主从再到多例,原来会发生这么多的问题_阳阳的博客-CSDN博客


R可重入与锁续期源码分析_阳阳的博客-CSDN博客


在使用C获取分布式锁时,C会在指定的下创建一个有序的临时节点,如果该节点是最小的,则代表获取锁成功。


接下来,在准备工作中,我们可以观察是否会创建出一个临时节点出来。


二、准备工作
首先我们需要搭建一个集群,当然你使用单机也行。


在这篇文章面试官:能给我画个Z选举的图吗?,介绍了一种使用-方式速搭建集群的方式。


在中引入依赖:








III-I2120



C客户端的配置项:


***@*@2022021122:59:4*@CCFC{各节点地址SCONNECT_STRING=:2181,:2182,:218;连接超时时间(单位:毫秒)CONNECTION_TIME_OUT_MS=10*1000;会话超时时间(单位:毫秒)SESSION_TIME_OUT_MS=0*1000;重试的初始等待时间(单位:毫秒)BASE_SLEEP_TIME_MS=2*1000;最大重试次数MAX_RETRIES=;@BCFCF(){CFF=CFF()S(CONNECT_STRING)TM(CONNECTION_TIME_OUT_MS)TM(SESSION_TIME_OUT_MS)P(EBR(BASE_SLEEP_TIME_MS,MAX_RETRIES))();F();F;}}
SESSION_TIME_OUT_MS参数则会保证,在某个客户端获取到锁之后突然宕机,能在该时间内删除当前客户端创建的临时有序节点。


测试代码如下:


临时节点路径,是博主字缩写哈SLOCK_PATH=;@RCFF;C()E{IPMPM=IPM(F,LOCK_PATH)M();{模拟业务耗时T(0*1000);}(E){ST();}{PM();}}
当使用接口调用该方法时,在T处打上断点,进入到容器中观察创建出来的节点。


使用-容器以交互模式进入容器,接着使用C连接到的端。


然后使用查看节点





这个节点都是持久节点,可以使用查看节点的数据结构信息





若一个节点的O值为0,即该节点的临时拥有者的会话为0,则代表该节点为持久节点。


当走到断点T时,确发现在下创建出来一个临时节点





̴到这里吗,准备工作已经做完了,接下来分析PM与的流程


、源码分析
C支持多种类型的锁,例如



IPM,可重入锁排它锁
IPRWL,读写锁
IPSM,不可重入排它锁

今天主要是分析IPM的加解锁过程,先看加锁过程


加锁
()E{(!L(-1,)){IOE(L:+P);}}
这里是阻塞式获取锁,获取不到锁,就一直进行阻塞。所以对于L方法,超时时间设置为-1,时间单位设置成。


L(,TU)E{TT=TT();通过能否在中取到该线程的LD信息,来判断该线程是否已经持有锁LDD=D(T);(D!=){进行可重入,直接返回加锁成功DCAG();;}进行加锁SP=L(,,LNB());(P!=){加锁成功,保存到中LDLD=LD(T,P);D(T,LD);;};}
其中D是一个,线程对象,为该线程绑定的锁数据。


LD中保存了加锁线程T,重入计数C与加锁路径P,例如


__4651-0-405-1-512847--0000000005
CMT,LDD=MCM();LD{TT;SP;AIC=AI(1);LD(TT,SP){T=T=P;}}
进入到L方法中


SL(,TU,[]NB)E{开始时间M=STM();将超时时间统一转化为毫秒单位LTW=(!=)?M():;节点数据,这里为[]LNB=(()!=)?[0]:NB;重试次数C=0;锁路径SP=;是否获取到锁TL=;是否完成D=;(!D){D=;{创建一个临时有序节点,并返回节点路径内部调用()PCIN()P()M(CMEPHEMERAL_SEQUENTIAL)P()=TL(,,LNB);依据返回的节点路径,判断是否抢到了锁TL=LL(M,TW,P);}(KENNE){在会话过期时,可能导致找不到临时有序节点,从而抛出NNE这里就进行重试(ZC()RP()R(C++,STM()-M,RLDRS())){D=;}{;}}}获取到锁,则返回节点路径,供调用方记录到中(TL){P;};}
接下来,将会在LL中利用刚才创建出来的临时有序节点,判断是否获取到了锁。


LL(M,LTW,SP)E{是否获取到锁TL=;D=;{(()!=){当前不会进入这里D()W(W)P(P);}一直尝试获取锁((S()==CFSSTARTED)!TL){返回P(这里是)下所有的临时有序节点,并且按照后缀从小到大排列LS=SC();取出当前线程创建出来的临时有序节点的称,这里就是__4651-0-405-1-512847--0000000005SNN=P(P()+1);判断当前节点是否处于排序后的首位,如果处于首位,则代表获取到了锁PRR=TL(,,NN,L);(RTL()){获取到锁之后,则终止循环TL=;}{这里代表没有获取到锁获取比当前节点索引小的前一个节点SSP=P++RPTW();(){{如果前一个节点不存在,则直接抛出NNE,中不进行处理,在下一轮中继续获取锁如果前一个节点存在,则给它设置一个**,监听它的释放事件D()W()P(SP);(TW!=){TW-=(STM()-M);M=STM();判断是否超时(TW=0){获取锁超时,删除刚才创建的临时有序节点D=;;}没超时的话,在TW内进行等待(TW);}{限期阻塞等待,监听到前一个节点被删除时,才会触发唤醒操作();}}(KENNE){如果前一个节点不存在,则直接抛出NNE,中不进行处理,在下一轮中继续获取锁}}}}}(E){TUI();D=;;}{(D){删除刚才创建出来的临时有序节点OP(P);}}TL;}
判断是否获取到锁的核心逻辑位于TL中


PRTL(CF,LS,SNN,L)E{获取当前节点在所有子节点排序后的索引位置I=O(NN);判断当前节点是否处于子节点中OI(NN,I);IPM的构造方法,会将L初始化为1I必须为0,才能使得TL为,也就是说,当前节点必须是P下的最小节点,才能代表获取到了锁TL=IL;如果获取不到锁,则返回上一个节点的称,用作对其设置监听STW=TL?I-L)R(TW,TL);}OI(SNN,I)KE{(I0){可能会由于连接丢失导致临时节点被删除,因此这里属于保险措施KENNE(S:+NN);}}
那什么时候,在LL处于的线程能被唤醒呢?


在LL方法中,已经使用


D()W()P(SP);
给前一个节点设置了**,当该节点被删除时,将会触发中的回调


W=W(){回调方法@O(WE){FW();}};FW(){唤醒所以在LI例上等待的线程A();}
到这里,基本上已经分析完加锁的过程了,在这里总结下:


首先创建一个临时有序节点


如果该节点是P下最小节点,则代表获取到了锁,存入中,下次直接进行重入。


如果该节点不是最小节点,则对前一个节点设置监听,接着进行等待。当前一个节点被删除时,将会通知该线程。


解锁
解锁的逻辑,就比较简单了,直接进入方法中


()E{TT=TT();LDD=D(T);(D==){IMSE(Y:+P);}LC=DCAG();直接减少一次重入次数(LC0){;}(LC0){IMSE(L:+P);}到这里代表重入次数为0{释放锁L(DP);}{从中移除D(T);}}L(SP)E{();内部使用,会在后台不断尝试删除节点OP(P);}
重入次数大于0,就减少重入次数。当减为0时,调用去删除节点,这一点和R可重入锁释放时一致。


四、羊群效应
在这里谈谈使用Z现分布式锁场景中的羊群效应


什么是羊群效应
首先,羊群是一种很散乱的组织,漫目的,缺少管理,一般需要牧羊犬来帮助主人控制羊群。


某个时候,当其中一只羊发现前面有更加美味的草而动起来,就会导致其余的羊一哄而上,根本不管周围的情况。


所以羊群效应,指的是一个人在进行理性的行为后,导致其余人直接盲从,产生非理性的从众行为。


而Z中的羊群效应,则是指一个被改变后,触发了大量本可以被避免的通知,造成集群资源的浪费。


获取不到锁时的等待演化
一段时间
如果某个线程在获取锁失败后,完全可以一段时间,再尝试获取锁。


但这样的方式,效率极低。


时间短的话,会频繁地进行轮询,浪费资源。


时间长的话,会出现锁被释放但仍然获取不到锁的尴尬情况。


所以,这里的优化点,在于如何变主动轮询为异步通知。


被锁住的节点
所有的客户端要获取锁时,只去创建一个同的。


当存在时,这些客户端对其设置监听。当被删除后,通知所有等待锁的客户端,接着这些客户端再次尝试获取锁。


虽然这里使用机制来异步通知,可是当客户端的数量特别多时,会存在性能低点。


当被删除后,在这一瞬间,需要给大量的客户端发送通知。在此期间,其余提交给的正常请求可能会被延迟或者阻塞。


这就产生了羊群效应,一个点的变化(被删除),造成了全面的影响(通知大量的客户端)。


所以,这里的优化点,在于如何减少对一个的监听数量,最好的情况是只有一个。


前一个有序节点
如果先指定一个P,想要获取锁的客户端,直接在该路径下创建临时有序节点。


当创建的节点是最小节点时,代表获取到了锁。如果不是最小的节点,则只对前一个节点设置**,只监听前一个节点的删除行为。


这样前一个节点被删除时,只会给下一个节点代表的客户端发送通知,不会给所有客户端发送通知,从而避免了羊群效应。





̴在避免羊群效应的同时,使得当前锁成为公平锁。即按照申请锁的先后顺序获得锁,避免存在饥饿过度的线程。


五、后语
源码角度讲解了使用C获取分布式锁的流程,接着从等待锁的演化过程角度出发,分析了Z在分布式锁场景下避免羊群效应的解决方案。


这是Z系列的第二篇,关于其原理分析、协议等文章也在安排的路上了。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

收藏:1 | 帖子:8万



侵权举报:本页面所涉内容均为用户发表并上传,岭南都会网仅提供存储服务,岭南都会网不承担相应的法律责任;如存在侵权问题,请权利人与岭南都会网联系删除!