首页 > 经验记录 > 探秘分布式解决方案: 分布式锁——咸鱼也能懂的Zookeeper分布式锁实现原理

探秘分布式解决方案: 分布式锁——咸鱼也能懂的Zookeeper分布式锁实现原理

Zookeeper 是一种分布式协调服务,在分布式环境中协调和管理服务是一个复杂的过程。ZooKeeper 通过其简单的架构和 API 解决了这个问题。ZooKeeper 允许开发人员专注于核心应用程序逻辑,而不必担心应用程序的分布式特性。
 
分布式协调服务主要用来解决分布式环境当中多个进程之间的同步控制,让他们有序的去访问某种临界资源,防止造成”脏数据”的后果。
为了防止分布式系统中的多个进程之间相互干扰,需要一种分布式协调技术来对这些进程进行调度。
而这个分布式协调技术的核心就是来实现这个分布式锁。
 

分布式锁应该具备哪些条件?

1、在分布式系统环境下,一段代码在同一时间只能被一个机器的一个线程执行
2、高可用的获取锁与释放锁
3、高性能的获取锁与释放锁
4、具备可重入特性(可理解为重新进入,由多于一个任务并发使用,而不必担心数据错误)
5、具备锁失效机制,防止死锁
6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
 

关于业界比较流行的分布式锁实现方案:

一般来说Redis会经常被提及到,但是Redis并不是天生为了实现分布式锁而设计出来的, 他是个NoSql内存数据库;不过我们可以利用一些Redis的特性来实现 分布式锁 这个需求
这个链接文章比较清晰的说了redis如何实现分布式锁和redis分布式锁面临的问题,这个说的很清楚了我就不详细说了。
http://zhangtielei.com/posts/blog-redlock-reasoning.html
 
那Zookeeper呢 ,雅虎工程师设计Zookeeper的初衷,就是为了实现分布式锁服务的,利用 Zookeeper 的顺序临时节点,来实现分布式锁和等待队列。当然Zookeeper不止这个功能,比如它可以用来实现服务发现与注册的功能,单以这个需求来使用Zookeeper的也是很多的。
 
在说Zookeeper实现分布式锁之前,首先来讲下他为啥可以实现分布式锁,以及Zookeeper的数据模型:
Zookeeper 的数据模型很像数据结构当中的树,也很像文件系统的目录。
树是由节点所组成,Zookeeper 的数据存储也同样是基于节点,这种节点叫做 Znode,但是,不同于树的节点,Znode 的引用方式是路径引用,类似于文件路径:/car/bmw   、/animal/cat
这样的层级结构,让每一个 Znode 节点拥有唯一的路径。
 

那么Znode 包含哪些元素呢?有这么些:

  • data:Znode 存储的数据信息。
  • ACL:记录 Znode 的访问权限,即哪些人或哪些 IP 可以访问本节点。
  • stat:包含 Znode 的各种元数据,比如事务 ID、版本号、时间戳、大小等等。
  • child:当前节点的子节点引用

Zookeeper 是为读多写少的场景所设计。Znode 并不是用来存储大规模业务数据,而是用于存储少量的状态和配置信息,每个节点数据最大只有1M。
关于Zookeeper的基本增删改查的操作就不详细说明了,主要有这么几种:create、delete、exists、getData、setData、getChildren。
而跟增删改查息息相关的,Zookeeper 客户端在请求读操作的时候,可以选择是否设置 Watch,也就是Zookeeper的事件通知机制
 
可以把 Watch 理解成是注册在特定 Znode 上的触发器。调用了 create、delete、setData 方法的时候 (做出修改操作时),将会触发 Znode 上注册的事件,请求 Watch 的客户端会接收到异步通知。
大概的事件通知机制如下:

  • 客户端调用 getData 方法,watch 参数是 true。服务端接到请求,返回节点数据,并且在对应的哈希表里插入被 Watch 的 Znode 路径,以及 Watcher 列表。
  • 当被 Watch 的 Znode 已删除,服务端会查找哈希表,找到该 Znode 对应的所有 Watcher,异步通知客户端,并且删除哈希表中对应的 Key-Value。

 
好,说了这么多,差不过该进入主题如何实现分布式锁了,上边说了利用 Zookeeper 的顺序临时节点可以实现分布式锁,那么这顺序临时节点又是个什么玩意?
默认创建的Znode  是”持久节点” 创建节点的客户端与 Zookeeper 断开连接后,该节点依旧存在。
除此之外,还有其他几种,其实看名字也能猜出是啥用了:
持久顺序节点:  所谓持久顺序节点,就是在创建节点时,Zookeeper 根据创建的时间顺序给该节点名称进行编号。其他和持久节点一样。
临时节点:  和持久节点相反,当创建节点的客户端与 Zookeeper 断开连接后,临时节点会被删除。
临时顺序节点: 顾名思义,临时节点和顺序节点的合成体;在创建节点时,Zookeeper 根据创建的时间顺序给该节点名称进行编号;当创建节点的客户端与 Zookeeper 断开连接后,节点会被删除。
 

Zookeeper 分布式锁的原理机制:

说完节点,也就差不多可以实现分布式锁了,其实知道这几个节点是个什么玩意后,在结合一下Watch机制,基本可以在脑内猜想个实现大概出来。

加锁释放锁流程:

1、首先,在 Zookeeper 当中创建一个持久节点,给它取个名字名字叫 ParentLock。当第一个客户端想要获得锁时,在 ParentLock 这个节点下面创建一个临时顺序节点 Lock1。之后,Client1 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 Lock1 是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁。
2、这时候,如果再有一个客户端 Client2 前来获取锁,则在 ParentLock 下再创建一个临时顺序节点 Lock2。
3、Client2 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 Lock2 是不是顺序最靠前的一个,结果发现节点 Lock2 并不是最小的。于是,Client2 向排序仅比它靠前的节点 Lock1 注册 Watcher,用于监听 Lock1 节点是否存在。这意味着 Client2 抢锁失败,进入了等待状态,这就形成了一个等待队列。
4、  当任务完成时,Client1 会显示调用删除节点 Lock1 的指令。此时由于 Client2 一直监听着 Lock1 的存在状态,当 Lock1 节点被删除,Client2 会立刻收到通知。这时候 Client2 会再次查询 ParentLock 下面的所有节点,确认自己创建的节点 Lock2 是不是目前最小的节点。如果是最小,则 Client2 顺理成章获得了锁。
5、如果说获得锁的 Client1 在任务执行过程中如果崩溃了,则会断开与 Zookeeper 服务端的链接。根据临时节点的特性,相关联的节点 Lock1 会随之自动删除;所以Client2 又收到通知获得了锁。
 
说到这,是不是觉得Zookeeper的机制来实现分布式锁较为不错,人家都给你封装好了,不用向Redis那样实现分布式锁还得考虑超时、原子性、误删除之类的。
还有等待队列可以提升抢锁效率,比起Redis实现的效果确实是舒服了许多。
 

           


CAPTCHAis initialing...
EA PLAYER &

历史记录 [ 注意:部分数据仅限于当前浏览器 ]清空

      00:00/00:00