最近在学习 Zookeeper,在刚开始接触 Zookeeper 的时候,彻底不知道 Zookeeper 有什么用。且不少资料都是将 Zookeeper 描述成一个“类 Unix/Linux 文件系统”的中间件,致使我很难将类 Unix/Linux 文件系统的 Zookeeper 和分布式应用联系在一块儿。后来在粗读了《ZooKeeper 分布式过程协同技术详解》和《从Paxos到Zookeeper 分布式一致性原理与实践》两本书,并动手写了一些 CURD demo 后,初步对 Zookeeper 有了必定的了解。不过比较肤浅,为了进一步加深对 Zookeeper 的认识,我利用空闲时间编写了本篇文章对应的 demo -- 基于 Zookeeper 的分布式锁实现。经过编写这个分布式锁 demo,使我对 Zookeeper 的 watcher 机制、Zookeeper 的用途等有了更进一步的认识。不过我所编写的分布式锁仍是比较简陋的,实现的也不够优美,仅仅是个练习,仅供参考使用。好了,题外话就说到这里,接下来咱们就来聊聊基于 Zookeeper 的分布式锁实现。git
在本章,我将分别说明独占锁和读写锁详细的实现过程,并配以相应的流程图帮助你们了解实现的过程。这里先说说独占锁的实现。github
独占锁又称排它锁,从字面意思上很容易理解他们的用途。即若是某个操做 O1 对访问资源 R1 的过程加锁,在操做 O1 结束对资源 R1 访问前,其余操做不容许访问资源 R1。以上算是对独占锁的简单定义了,那么这段定义在 Zookeeper 的“类 Unix/Linux 文件系统”的结构中是怎样实现的呢?在锁答案前,咱们先看张图:分布式
图1 独占锁的 Zookeeper 节点结构性能
如上图,对于独占锁,咱们能够将资源 R1 看作是 lock 节点,操做 O1 访问资源 R1 看作建立 lock 节点,释放资源 R1 看作删除 lock 节点。这样咱们就将独占锁的定义对应于具体的 Zookeeper 节点结构,经过建立 lock 节点获取锁,删除节点释放锁。详细的过程以下:学习
上面即独占锁具体的实现步骤,理解起来并不复杂,这里再也不赘述。优化
图2 获取独占锁流程图spa
说完独占锁的实现,这节来讲说读写锁的实现。读写锁包含一个读锁和写锁,操做 O1 对资源 R1 加读锁,且得到了锁,其余操做可同时对资源 R1 设置读锁,进行共享读操做。若是操做 O1 对资源 R1 加写锁,且得到了锁,其余操做再对资源 R1 设置不一样类型的锁都会被阻塞。总结来讲,读锁具备共享性,而写锁具备排他性。那么在 Zookeeper 中,咱们能够用怎样的节点结构实现上面的操做呢?code
图3 读写锁的 Zookeeper 节点结构中间件
在 Zookeeper 中,因为读写锁和独占锁的节点结构不一样,读写锁的客户端不用再去竞争建立 lock 节点。因此在一开始,全部的客户端都会建立本身的锁节点。若是不出意外,全部的锁节点都能被建立成功,此时锁节点结构如图3所示。以后,客户端从 Zookeeper 端获取 /share_lock 下全部的子节点,并判断本身可否获取锁。若是客户端建立的是读锁节点,获取锁的条件(知足其中一个便可)以下:blog
若是客户端建立的是写锁节点,因为写锁具备排他性。因此获取锁的条件要简单一些,只需肯定本身建立的锁节点是否排在其余子节点前面便可。
不一样于独占锁,读写锁的实现稍微复杂一下。读写锁有两种实现方式,各有异同,接下来就来讲说这两种实现方式。
第一种实现是对 /share_lock 节点设置 watcher,当 /share_lock 下的子节点被删除时,未获取锁的客户端收到 /share_lock 子节点变更的通知。在收到通知后,客户端从新判断本身建立的子节点是否能够获取锁,若是失败,再次等待通知。详细流程以下:
上述步骤对于的流程图以下:
图4 获取读写锁实现1流程图
上面获取读写锁流程并不复杂,但却存在性能问题。以图3所示锁节点结构为例,第一个锁节点 host1-W-0000000001 被移除后,Zookeeper 会将 /share_lock 子节点变更的通知分发给全部的客户端。但实际上,该子节点变更通知除了能影响 host2-R-0000000002 节点对应的客户端外,分发给其余客户端则是在作无用功,由于其余客户端即便获取了通知也没法获取锁。因此这里须要作一些优化,优化措施是让客户端只在本身关心的节点被删除时,再去获取锁。
在了解读写锁第一种实现的弊端后,咱们针对这一实现进行优化。这里客户端再也不对 /share_lock 节点进行监视,而只对本身关心的节点进行监视。仍是以图3的锁节点结构进行举例说明,host2-R-0000000002 对应的客户端 C2 只需监视 host1-W-0000000001 节点是否被删除便可。而 host3-W-0000000003 对应的客户端 C3 只需监视 host2-R-0000000002 节点是否被删除便可,只有 host2-R-0000000002 节点被删除,客户端 C3 才能获取锁。而 host1-W-0000000001 节点被删除时,产生的通知对于客户端 C3 来讲是无用的,即便客户端 C3 响应了通知也无法获取锁。这里总结一下,不一样客户端关心的锁节点是不一样的。若是客户端建立的是读锁节点,那么客户端只需找出比读锁节点序号小的最后一个的写锁节点,并设置 watcher 便可。而若是是写锁节点,则更简单,客户端仅需对该节点的上一个节点设置 watcher 便可。详细的流程以下:
上述步骤对于的流程图以下:
图5 获取读写锁实现2流程图
本文较为详细的描述了基于 Zookeeper 分布式锁的实现过程,并根据上面描述的两种锁原理实现了较为简单的分布式锁 demo,代码放在了 github 上,须要的朋友自取。由于这只是一个简单的 demo,代码实现的并不优美,仅供参考。最后,若是你以为文章还不错的话,欢迎点赞。若是有不妥的地方,也请提出来,我会虚心改之。好了,最后祝你们生活愉快,再见。
本文在知识共享许可协议 4.0 下发布,转载请注明出处
做者:coolblog
为了得到更好的分类阅读体验,
请移步至本人的我的博客: http://www.coolblog.xyz
本做品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。