画分布式锁之"通文馆圣主"Curator的"十三太保"(上)

      上文,我们已经基于图文分析了zookeeper实现分布式锁的基本原理,【画分布式锁之Zookeeper实现机制 】,文末也引出了zookeeper一款强大的客户端框架--Curator,看它的命名也看出了一些乐趣,翻译成中文,叫做馆长,zookeeper当年是因为管理了很多动物命名的分布式组件,才命名成动物管理员,而Curator是馆长,是动物园的园长,这也体现除了改客户端框架的强大,Curator实现了zookeeper的很多核心功能,分布式锁也因为zookeeper的天生优势,让Curator使用起来得心应手,难有敌手。【引用:通文馆是动漫不良人里面的一个势力,十三太保,除了俩位领头的除外,剩余各自都有强大的武力值,对应仁、义、礼、智、信、忠、孝、惠、勇、忍】,那我们这位馆长的实力如何呢?接下来我们就来剖析馆长的强大武力。

       事先,我已经基于Zookeeper3.4.9部署安装了三个实例,安装过程较为简单,也没有遇到什么坑,这里就不展开阐述了,Curator我们选用2.3.0版本进行分析。

     【仁,义--互斥锁&& 加锁&&可重入

      首先呢,我们先来看看上图,先创建了一个zookeeper的客户端,并且启动,接下来就是创建了一个可重入锁对象,我们就先来分析InterProcessMutex加锁的过程,调用【acquire()】方法,我们可以跟到下一步去看看加锁的核心流程。

调用【internalLock()】方法,如果返回结果是false,就会抛出连接断开的异常,我们跟进去看看,首先获取了当前的线程对象,并且在该对象

的成员变量里面有一个ConcurrentMap threadData,就是将获取到的锁对象和当前线程绑定起来,我们这里是第一次加锁,所以在threadData中是拿不到锁信息的。我们可以看到,如果我们当前的线程如果是有锁信息的话,那么是将锁对象中的lockCount属性线程安全的加一,然后就返回了。这说明什么呢?结论:InterProcessMutex 互斥锁是可重入的】,如若没有锁对象信息,就会尝试加锁,调用【 attemptLock() 】方法,此时传入的time是-1,unit为null,进入方法,先做了一些时间的计算,开始时间,等待时间为null,

retryCount尝试次数为0,紧接着就进入了while循环,先是将isDone置为true,此时,localLockNodeBytes=null,进入else分支,以path="/locks/lock_01/lock-"调用API创建了一个临时顺序节点,返回的ourpath="/locks/lock_01/_c_077f05d3-9689-4183-8192-aa1884990e9a-lock-0000000005",接下来就是调用【internalLockLoop()】方法,此时客户端状态是STARTED,而且还没有获取到锁,hasTheLock=false,进入while循环,首

先调用【getSortedChildren()】方法去获取父节点下所有子节点并且排序后的列表,此时返回的childrenList为空,紧接着获取sequenceNodeName=“_c_077f05d3-9689-4183-8192-aa1884990e9a-lock-0000000005

maxLeases=1,调用【getTheLock()】方法,先判断我们的节点在有序队

列中是第几个,如果ourIndex小于0,则会抛出sequential path not found 异常。

这里有一个关键的判断,【 boolean getsTheLock=ourIndex < maxLeases 】,如果返回为true 就说明获取锁成功了,监控路径pathToWatch=null;如果返回值为false ,pathToWatch=children.get(ourIndex-maxLeases);到这里封装了一个对象PredicateResults,将pathToWatch和getsTheLock传入构造参数。紧接着就是根据对象PredicateResults的属性getsTheLock判断是否获取锁成功,将haveTheLock=true;然后就是一路返回,并且将线程和锁路径绑定到LockData对象中,然后放入到CurrentHashMap threadData中,加锁成功。

      【 礼,智--互斥锁 && 锁释放 &&锁监听

        客户端释放锁的流程很简单,先从本地的缓存map中根据线程获

取对应的所对象信息,如果lockData=null,就会抛出异常,否则就会将锁对象中的lockCount-1并返回当前的newLockCount,如果newLockCount>0,这里也是可以体现【互斥锁是具备可重入特性的 】,紧接着就是调用API去删除锁,这里会保证锁路径被删除,最终,都会将本地缓存的线程对应的锁对象remove。到这里释放锁流程就结束了。

那么当前有一个客户端持有锁的时候,其他线程来加锁呢?首先有变化的就是之前的【getSortedChildren()】方法获取的childList会有俩个对象,并且当前客户端sequenceNodeName对应在列表中不是第一个,此时,getstheLock=false;此时pathToWatch=/locks/lock_01/_c_077f05d3-9689-4183-8192-aa1884990e9a-lock-0000000005,还是封装了一个结果对象返回,这个时候加锁是不成功的,进入到else分支,previousSequencePath=pathToWatch,使用watcher去监听这个节点的

状态,然后就会陷入等待,直到被监听的那个节点被删除了,就会通知监听器,并且唤醒所有等待的客户端,其实就是调用了notifyAll(),唤醒之前所有陷入等待的线程,再一次循环进行获取子节点,判断是否是第一个,是否加锁成功的流程。

      【 信--互斥锁 && 公平锁

       因为每一个客户端创建的都是有序的临时节点,也可以说这里的互斥锁就是公平锁,都会按照自己申请锁的顺序来排序,最后也会按照自己的顺序取获取锁,监听自己前一个节点。结论:【InterProcessMutex互斥锁是公平锁,每个加锁客户端排队获取锁】。

     【忠,孝--信号量 && 加锁 && 释放锁】

       信号量,Semaphore,之前我们也讲过他的实现,就是可以在锁初始化的时候,就可以指定同时有多少个线程获取到锁,那么Curator是怎么实现的呢,我们来瞧一瞧,示例很简单啦,我们先指定同时可以有三个线程,可以获取到锁,我们就debug一下他的流程。

        首先构造完InterProcessSemaphoreV2实例之后,首先有个成员变量我们要重点关注一下,就是leasesPath=/semaphores/semaphore_01/leases,这个就是信号量加锁要操作的根目录。紧接着我们看加锁逻辑,先是时间计算等待时

waitMs=0;此时qyt=1,进入while循环,isDone=false,进入while循环,调用【 internalAcquire1Lease()】方法,此时客户端的状态是STARTED,

hasWait=false,走下一个分支,【lock.acquire()】,这边的这个方法走的还是我们上面的锁的流程,这里就不去细说,可以看看上文已做回顾,最主要就是在/semaphores/semaphore_01/locks/目录下创建一个节点,lock=/semaphores/semaphore_01/locks/_c_9e929d50-ada8-4c13-b7eb-a1236b34c2d9-lock-0000000006这个锁也只能在同一时间只有一个客户端可以获取到。其他的客户端要按照排队顺序等待。

     然后就是一个构造器创建了一个节点path,这个就是lease下的顺序节点/semaphores/semaphore_01/leases/_c_9af30409-c4f3-4cd3-9cb9-439c46d4f1ff-lease-0000000003,然后基于nodeName构造一个Lease节点对象。紧接着就是一个无限循环,先是将/semaphores/semaphore_01/leases/下所有的子节点都查出来,如果前面的nodeName不在子节点列表中,就直接抛异常返回;否则,这里就是要判断【children.size <=maxLeases】,maxLeases=3,这里就是把控当前最多多少个客户端线程同时加锁的地方,如果返回true,直接break;如果返回false,就会等待wait;有个finally分支,会将lock释放掉,返回到上个方法,switch匹配到Continue,isDone=true,并且break;将success置为true,这里就加锁成功了。

       释放锁的时候,调用【returnLease()】方法,这里就会去调用上面封装的Lease对象的【close()】,就会将该客户端申请的锁节点给删除掉。

       我们可以总结一下,信号量锁,首先先是借助了一把普通互斥锁lock,让所有的加锁客户端都能排队加锁,然后通过maxLeases这个属性去判断最大允许加锁的数量,就实现了信号量的加锁机制。

    【惠,非可重入锁 && 加锁

      非可重入锁,不支持可重入,其他的原理和互斥锁一样,我们看一下示例,名字很特殊,和信号量有着紧密的联系,我们可以来看看他的加锁

逻辑。我们惊奇的发现其实不可重入锁的实现借助了信号量,只是将变量maxLeases=1,这样就保证了,同一时间只有一个客户端可以加锁,其他人都要排队,同一个客户端的线程也不支持重入,很机智的idea哦。

释放锁就更简单了直接就是调用lease对象的close方法。

       到这里呢,我们就分析了Curator的门下的可重入互斥锁,非可重入锁,信号量锁,对这些锁做了源码解析,分析了Curator是如何实现这些锁的,文中所写如有问题,欢迎留言探讨,批评指正。预告:下一篇我们将继续分析Curator门下剩余的几把锁,感谢大家阅读。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容