1. 背景

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

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

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

2.1 独占锁的实现

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

图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 获取独占锁流程图

2.2 读写锁的实现

说完独占锁的实现,这节来说说读写锁的实现。读写锁包含一个读锁和写锁,操作 O1 对资源 R1 加读锁,且获得了锁,其他操作可同时对资源 R1 设置读锁,进行共享读操作。如果操作 O1 对资源 R1 加写锁,且获得了锁,其他操作再对资源 R1 设置不同类型的锁都会被阻塞。总结来说,读锁具有共享性,而写锁具有排他性。那么在 Zookeeper 中,我们可以用怎样的节点结构实现上面的操作呢?

图3 读写锁的 Zookeeper 节点结构

在 Zookeeper 中,由于读写锁和独占锁的节点结构不同,读写锁的客户端不用再去竞争创建 lock 节点。所以在一开始,所有的客户端都会创建自己的锁节点。如果不出意外,所有的锁节点都能被创建成功,此时锁节点结构如图3所示。之后,客户端从 Zookeeper 端获取 /share_lock 下所有的子节点,并判断自己能否获取锁。如果客户端创建的是读锁节点,获取锁的条件(满足其中一个即可)如下:

  1. 自己创建的节点序号排在所有其他子节点前面
  2. 自己创建的节点前面无写锁节点

如果客户端创建的是写锁节点,由于写锁具有排他性。所以获取锁的条件要简单一些,只需确定自己创建的锁节点是否排在其他子节点前面即可。

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

读写锁的第一种实现

第一种实现是对 /share_lock 节点设置 watcher,当 /share_lock 下的子节点被删除时,未获取锁的客户端收到 /share_lock 子节点变动的通知。在收到通知后,客户端重新判断自己创建的子节点是否可以获取锁,如果失败,再次等待通知。详细流程如下:

  1. 所有客户端创建自己的锁节点
  2. 从 Zookeeper 端获取 /share_lock 下所有的子节点,并对 /share_lock 节点设置 watcher
  3. 判断自己创建的锁节点是否可以获取锁,如果可以,持有锁。否则继续等待
  4. 持有锁的客户端删除自己的锁节点,其他客户端收到 /share_lock 子节点变动的通知
  5. 重复步骤2、3、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 分布式一致性原理与实践》

基于 Zookeeper 的分布式锁实现的更多相关文章

  1. 【连载】redis库存操作,分布式锁的四种实现方式[一]--基于zookeeper实现分布式锁

    一.背景 在电商系统中,库存的概念一定是有的,例如配一些商品的库存,做商品秒杀活动等,而由于库存操作频繁且要求原子性操作,所以绝大多数电商系统都用Redis来实现库存的加减,最近公司项目做架构升级,以 ...

  2. 分布式锁(3) ----- 基于zookeeper的分布式锁

    分布式锁系列文章 分布式锁(1) ----- 介绍和基于数据库的分布式锁 分布式锁(2) ----- 基于redis的分布式锁 分布式锁(3) ----- 基于zookeeper的分布式锁 代码:ht ...

  3. 基于Zookeeper的分布式锁(干干干货)

    原文地址: https://juejin.im/post/5df883d96fb9a0163514d97f 介绍 为什么使用锁 锁的出现是为了解决资源争用问题,在单进程环境下的资源争夺可以使用 JDK ...

  4. 基于Zookeeper的分布式锁

    实现分布式锁目前有三种流行方案,分别为基于数据库.Redis.Zookeeper的方案,其中前两种方案网络上有很多资料可以参考,本文不做展开.我们来看下使用Zookeeper如何实现分布式锁. 什么是 ...

  5. 10分钟看懂!基于Zookeeper的分布式锁

    实现分布式锁目前有三种流行方案,分别为基于数据库.Redis.Zookeeper的方案,其中前两种方案网络上有很多资料可以参考,本文不做展开.我们来看下使用Zookeeper如何实现分布式锁. 什么是 ...

  6. 基于ZooKeeper实现——分布式锁与实现

    引言 ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供一致性服务的软件,提 ...

  7. 基于zookeeper实现分布式锁和基于redis实现分布所的区别

    1,实现方式不同 zookeeper实现分布式锁:通过创建一个临时节点,创建的成功节点的服务则抢占到分布式锁,可做业务逻辑.当业务逻辑完成,连接中断,节点消失,继续下一轮的锁的抢占. redis实现分 ...

  8. 基于ZooKeeper的分布式锁和队列

    在分布式系统中,往往需要一些分布式同步原语来做一些协同工作,上一篇文章介绍了Zookeeper的基本原理,本文介绍下基于Zookeeper的Lock和Queue的实现,主要代码都来自Zookeeper ...

  9. 基于zookeeper实现分布式锁

    Zookeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Hadoop和Hbase的重要组件. 特性: 1.节点数据结构,znode是一个跟Unix文件系统路径相似的节点,可以往这个节点存 ...

随机推荐

  1. matlab 图像平移操作

    目标:对原图I进行[80,50]的偏移操作得到图B. 首先读入图像,以matlab自带的pout.tif为例. strel是创建形态学结构元素的. translate函数在原结构上进行[80,50]的 ...

  2. java -ui自动化初体验

    本文来讲一下ui自动化的环境搭建,以及最初级的打开网页操作 说起ui自动化,想想大概是前年的时候我开始接触和学习的吧,怎么说呢无论是pc还是app,ios还是android,确实很神奇而且很华丽,但是 ...

  3. tar解压到指定目录

    对于tar.gz的压缩包,压缩参数是tar xvzf 指定解压路径为/tmp则为: tar xzvf xxx.tar.gz -C /tmp 注意/文件夹必须存在.

  4. [SCOI2015]小凸玩矩阵

    Description: 给你一个n*m的网格,每个格子有一个数字,每行每列只能选一个数字,问所选数字中第k大的数字的最小值是多少 Hint: \(n \le 250\) Solution: 显然是二 ...

  5. Linux结束进程到底有多少种方法?

    我们经常在Linux里使用kill命令来结束某后台进程.但kill命令实际上是向进程发送信号,并且有多种信号.终止运行一个程序只是其中一个信号而已.kill是根据进程号发送信号的,而另一个工具kill ...

  6. centos7安装kubeadm

    安装配置docker v1.9.0版本推荐使用docker v1.12, v1.11, v1.13, 17.03也可以使用,再高版本的docker可能无法正常使用. 测试发现17.09无法正常使用,不 ...

  7. Scala语言笔记 - 第一篇

    目录 Scala语言笔记 - 第一篇 1 基本类型和循环的使用 2 String相关 3 模式匹配相关 4 class相关 5 函数调用相关 Scala语言笔记 - 第一篇 ​ 最近研究了下scala ...

  8. VIM常用快捷键(转载)

    移动光标 h,j,k,l 上,下,左,右 ctrl-e 移动页面 ctrl-f 上翻一页 ctrl-b 下翻一页 ctrl-u 上翻半页 ctrl-d 下翻半页 w 跳到下一个字首,按标点或单词分割 ...

  9. C# INI文件读写类

    public class Ini { // 声明INI文件的写操作函数 WritePrivateProfileString() [System.Runtime.InteropServices.DllI ...

  10. 通过ffi在node.js中调用动态链接库[转]

    http://blog.csdn.net/zhulin2609/article/details/51474676