基于 Zookeeper 的分布式锁实现

1. 背景

最近在学习 Zookeeper,在刚开始接触 Zookeeper 的时候,彻底不知道 Zookeeper 有什么用。且不少资料都是将 Zookeeper 描述成一个“类 Unix/Linux 文件系统”的中间件,致使我很难将类 Unix/Linux 文件系统的 Zookeeper 和分布式应用联系在一块儿。后来在粗读了《ZooKeeper 分布式过程协同技术详解》和《从Paxos到Zookeeper 分布式一致性原理与实践》两本书,并动手写了一些 CURD demo 后,初步对 Zookeeper 有了必定的了解。不过比较肤浅,为了进一步加深对 Zookeeper 的认识,我利用空闲时间编写了本篇文章对应的 demo -- 基于 Zookeeper 的分布式锁实现。经过编写这个分布式锁 demo,使我对 Zookeeper 的 watcher 机制、Zookeeper 的用途等有了更进一步的认识。不过我所编写的分布式锁仍是比较简陋的,实现的也不够优美,仅仅是个练习,仅供参考使用。好了,题外话就说到这里,接下来咱们就来聊聊基于 Zookeeper 的分布式锁实现。git

2. 独占锁和读写锁的实现

在本章,我将分别说明独占锁和读写锁详细的实现过程,并配以相应的流程图帮助你们了解实现的过程。这里先说说独占锁的实现。github

2.1 独占锁的实现

独占锁又称排它锁,从字面意思上很容易理解他们的用途。即若是某个操做 O1 对访问资源 R1 的过程加锁,在操做 O1 结束对资源 R1 访问前,其余操做不容许访问资源 R1。以上算是对独占锁的简单定义了,那么这段定义在 Zookeeper 的“类 Unix/Linux 文件系统”的结构中是怎样实现的呢?在锁答案前,咱们先看张图:分布式

独占锁的 Zookeeper 节点结构

图1 独占锁的 Zookeeper 节点结构性能

如上图,对于独占锁,咱们能够将资源 R1 看作是 lock 节点,操做 O1 访问资源 R1 看作建立 lock 节点,释放资源 R1 看作删除 lock 节点。这样咱们就将独占锁的定义对应于具体的 Zookeeper 节点结构,经过建立 lock 节点获取锁,删除节点释放锁。详细的过程以下:学习

  1. 多个客户端竞争建立 lock 临时节点
  2. 其中某个客户端成功建立 lock 节点,其余客户端对 lock 节点设置 watcher
  3. 持有锁的客户端删除 lock 节点或该客户端崩溃,由 Zookeeper 删除 lock 节点
  4. 其余客户端得到 lock 节点被删除的通知
  5. 重复上述4个步骤,直至无客户端在等待获取锁了

上面即独占锁具体的实现步骤,理解起来并不复杂,这里再也不赘述。优化

获取独占锁流程图

图2 获取独占锁流程图spa

2.2 读写锁的实现

说完独占锁的实现,这节来讲说读写锁的实现。读写锁包含一个读锁和写锁,操做 O1 对资源 R1 加读锁,且得到了锁,其余操做可同时对资源 R1 设置读锁,进行共享读操做。若是操做 O1 对资源 R1 加写锁,且得到了锁,其余操做再对资源 R1 设置不一样类型的锁都会被阻塞。总结来讲,读锁具备共享性,而写锁具备排他性。那么在 Zookeeper 中,咱们能够用怎样的节点结构实现上面的操做呢?code

图3 读写锁的 Zookeeper 节点结构中间件

在 Zookeeper 中,因为读写锁和独占锁的节点结构不一样,读写锁的客户端不用再去竞争建立 lock 节点。因此在一开始,全部的客户端都会建立本身的锁节点。若是不出意外,全部的锁节点都能被建立成功,此时锁节点结构如图3所示。以后,客户端从 Zookeeper 端获取 /share_lock 下全部的子节点,并判断本身可否获取锁。若是客户端建立的是读锁节点,获取锁的条件(知足其中一个便可)以下:blog

  1. 本身建立的节点序号排在全部其余子节点前面
  2. 本身建立的节点前面无写锁节点

若是客户端建立的是写锁节点,因为写锁具备排他性。因此获取锁的条件要简单一些,只需肯定本身建立的锁节点是否排在其余子节点前面便可。

不一样于独占锁,读写锁的实现稍微复杂一下。读写锁有两种实现方式,各有异同,接下来就来讲说这两种实现方式。

读写锁的第一种实现

第一种实现是对 /share_lock 节点设置 watcher,当 /share_lock 下的子节点被删除时,未获取锁的客户端收到 /share_lock 子节点变更的通知。在收到通知后,客户端从新判断本身建立的子节点是否能够获取锁,若是失败,再次等待通知。详细流程以下:

  1. 全部客户端建立本身的锁节点
  2. 从 Zookeeper 端获取 /share_lock 下全部的子节点,并对 /share_lock 节点设置 watcher
  3. 判断本身建立的锁节点是否能够获取锁,若是能够,持有锁。不然继续等待
  4. 持有锁的客户端删除本身的锁节点,其余客户端收到 /share_lock 子节点变更的通知
  5. 重复步骤二、三、4,直至无客户端在等待获取锁了

上述步骤对于的流程图以下:

图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 便可。详细的流程以下:

  1. 全部客户端建立本身的锁节点
  2. 从 Zookeeper 端获取 /share_lock 下全部的子节点
  3. 判断本身建立的锁节点是否能够获取锁,若是能够,持有锁。不然对本身关心的锁节点设置 watcher
  4. 持有锁的客户端删除本身的锁节点,某个客户端收到该节点被删除的通知,并获取锁
  5. 重复步骤4,直至无客户端在等待获取锁了

上述步骤对于的流程图以下:

图5 获取读写锁实现2流程图

3. 写在最后

本文较为详细的描述了基于 Zookeeper 分布式锁的实现过程,并根据上面描述的两种锁原理实现了较为简单的分布式锁 demo,代码放在了 github 上,须要的朋友自取。由于这只是一个简单的 demo,代码实现的并不优美,仅供参考。最后,若是你以为文章还不错的话,欢迎点赞。若是有不妥的地方,也请提出来,我会虚心改之。好了,最后祝你们生活愉快,再见。

参考

  • 《ZooKeeper 分布式过程协同技术详解》
  • 《从Paxos到Zookeeper 分布式一致性原理与实践》
本文在知识共享许可协议 4.0 下发布,转载请注明出处
做者:coolblog
为了得到更好的分类阅读体验,
请移步至本人的我的博客: http://www.coolblog.xyz

知识共享许可协议
本做品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。

相关文章
相关标签/搜索