zookeeper 分布式锁原理:java
1 你们也许都很熟悉了多个线程或者多个进程间的共享锁的实现方式了,可是在分布式场景中咱们会面临多个Server之间的锁的问题,实现的复杂度比较高。利用基于google chubby原理开发的开源的zookeeper,可使得这个问题变得简单不少。下面介绍几种可能的实现方式,而且对比每种实现方式的优缺点。node
1. 利用节点名称的惟一性来实现共享锁算法
ZooKeeper抽象出来的节点结构是一个和unix文件系统相似的小型的树状的目录结构。ZooKeeper机制规定:同一个目录下只能有一个惟一的文件名。例如:咱们在Zookeeper目录/test目录下建立,两个客户端建立一个名为Lock节点,只有一个可以成功。服务器
算法思路: 利用名称惟一性,加锁操做时,只须要全部客户端一块儿建立/test/Lock节点,只有一个建立成功,成功者得到锁。解锁时,只需删除/test/Lock节点,其他客户端再次进入竞争建立节点,直到全部客户端都得到锁。并发
基于以上机制,利用节点名称惟一性机制的共享锁算法流程如图所示:分布式
该共享锁实现很符合咱们一般多个线程去竞争锁的概念,利用节点名称惟一性的作法简明、可靠。性能
由上述算法容易看出,因为客户端会同时收到/test/Lock被删除的通知,从新进入竞争建立节点,故存在"惊群现象"。测试
使用该方法进行测试锁的性能列表以下:ui
总结 这种方案的正确性和可靠性是ZooKeeper机制保证的,实现简单。缺点是会产生“惊群”效应,假如许多客户端在等待一把锁,当锁释放时候全部客户端都被唤醒,仅仅有一个客户端获得锁。google
2. 利用临时顺序节点实现共享锁的通常作法
首先介绍一下,Zookeeper中有一种节点叫作顺序节点,故名思议,假如咱们在/lock/目录下建立节3个点,ZooKeeper集群会按照提起建立的顺序来建立节点,节点分别为/lock/000000000一、/lock/000000000二、/lock/0000000003。
ZooKeeper中还有一种名为临时节点的节点,临时节点由某个客户端建立,当客户端与ZooKeeper集群断开链接,则开节点自动被删除。
利用上面这两个特性,咱们来看下获取实现分布式锁的基本逻辑:
释放锁的过程相对比较简单,就是删除本身建立的那个子节点便可。
上面这个分布式锁的实现中,大致可以知足了通常的分布式集群竞争锁的需求。这里说的通常性场景是指集群规模不大,通常在10台机器之内。
不过,细想上面的实现逻辑,咱们很容易会发现一个问题,步骤4,“即获取全部的子点,判断本身建立的节点是否已是序号最小的节点”,这个过程,在整个分布式锁的竞争过程当中,大量重复运行,而且绝大多数的运行结果都是判断出本身并不是是序号最小的节点,从而继续等待下一次通知——这个显然看起来不怎么科学。客户端无故的接受到过多的和本身不相关的事件通知,这若是在集群规模大的时候,会对Server形成很大的性能影响,而且若是一旦同一时间有多个节点的客户端断开链接,这个时候,服务器就会像其他客户端发送大量的事件通知——这就是所谓的惊群效应。而这个问题的根源在于,没有找准客户端真正的关注点。
咱们再来回顾一下上面的分布式锁竞争过程,它的核心逻辑在于:判断本身是不是全部节点中序号最小的。因而,很容易能够联想的到的是,每一个节点的建立者只须要关注比本身序号小的那个节点。
3. 利用临时顺序节点实现共享锁的改进实现
下面是改进后的分布式锁实现,和以前的实现方式惟一不一样之处在于,这里设计成每一个锁竞争者,只须要关注”locknode”节点下序号比本身小的那个节点是否存在便可。
算法思路:对于加锁操做,可让全部客户端都去/lock目录下建立临时顺序节点,若是建立的客户端发现自身建立节点序列号是/lock/目录下最小的节点,则得到锁。不然,监视比本身建立节点的序列号小的节点(比本身建立的节点小的最大节点),进入等待。
对于解锁操做,只须要将自身建立的节点删除便可。
具体算法流程以下图所示:
使用上述算法进行测试的的结果以下表所示:
该算法只监控比自身建立节点序列号小(比本身小的最大的节点)的节点,在当前得到锁的节点释放锁的时候没有“惊群”。
总结 利用临时顺序节点来实现分布式锁机制其实就是一种按照建立顺序排队的实现。这种方案效率高,避免了“惊群”效应,多个客户端共同等待锁,当锁释放时只有一个客户端会被唤醒。
4. 使用menagerie
其实就是对方案3的一个封装,不用本身写代码了。直接拿来用就能够了。
menagerie基于Zookeeper实现了java.util.concurrent包的一个分布式版本。这个封装是更大粒度上对各类分布式一致性使用场景的抽象。其中最基础和经常使用的是一个分布式锁的实现: org.menagerie.locks.ReentrantZkLock,经过ZooKeeper的全局有序的特性和EPHEMERAL_SEQUENTIAL类型znode的支持,实现了分布式锁。具体作法是:不一样的client上每一个试图得到锁的线程,都在相同的basepath下面建立一个EPHEMERAL_SEQUENTIAL的node。EPHEMERAL表示要建立的是临时znode,建立链接断开时会自动删除; SEQUENTIAL表示要自动在传入的path后面缀上一个自增的全局惟一后缀,做为最终的path。所以对不一样的请求ZK会生成不一样的后缀,并分别返回带了各自后缀的path给各个请求。由于ZK全局有序的特性,无论client请求怎样前后到达,在ZKServer端都会最终排好一个顺序,所以自增后缀最小的那个子节点,就对应第一个到达ZK的有效请求。而后client读取basepath下的全部子节点和ZK返回给本身的path进行比较,当发现本身建立的sequential node的后缀序号排在第一个时,就认为本身得到了锁;不然的话,就认为本身没有得到锁。这时确定是有其余并发的而且是没有断开的client/线程先建立了node。