2、
接下来咱们一块儿来看看,多客户端获取及释放zk分布式锁的整个流程及背后的原理。node
首先你们看看下面的图,若是如今有两个客户端一块儿要争抢zk上的一把分布式锁,会是个什么场景?redis
若是你们对zk还不太了解的话,建议先自行百度一下,简单了解点基本概念,好比zk有哪些节点类型等等。框架
参见上图。zk里有一把锁,这个锁就是zk上的一个节点。而后呢,两个客户端都要来获取这个锁,具体是怎么来获取呢?分布式
我们就假设客户端A抢先一步,对zk发起了加分布式锁的请求,这个加锁请求是用到了zk中的一个特殊的概念,叫作“临时顺序节点”。性能
简单来讲,就是直接在"my_lock"这个锁节点下,建立一个顺序节点,这个顺序节点有zk内部自行维护的一个节点序号。spa
好比说,第一个客户端来搞一个顺序节点,zk内部会给起个名字叫作:xxx-000001。而后第二个客户端来搞一个顺序节点,zk可能会起个名字叫作:xxx-000002。你们注意一下,最后一个数字都是依次递增的,从1开始逐次递增。zk会维护这个顺序。线程
因此这个时候,假如说客户端A先发起请求,就会搞出来一个顺序节点,你们看下面的图,Curator框架大概会弄成以下的样子:设计
你们看,客户端A发起一个加锁请求,先会在你要加锁的node下搞一个临时顺序节点,这一大坨长长的名字都是Curator框架本身生成出来的。code
而后,那个最后一个数字是"1"。你们注意一下,由于客户端A是第一个发起请求的,因此给他搞出来的顺序节点的序号是"1"。排序
接着客户端A建立完一个顺序节点。还没完,他会查一下"my_lock"这个锁节点下的全部子节点,而且这些子节点是按照序号排序的,这个时候他大概会拿到这么一个集合:
接着客户端A会走一个关键性的判断,就是说:唉!兄弟,这个集合里,我建立的那个顺序节点,是否是排在第一个啊?
若是是的话,那我就能够加锁了啊!由于明明我就是第一个来建立顺序节点的人,因此我就是第一个尝试加分布式锁的人啊!
bingo!加锁成功!你们看下面的图,再来直观的感觉一下整个过程。
接着假如说,客户端A都加完锁了,客户端B过来想要加锁了,这个时候他会干同样的事儿:先是在"my_lock"这个锁节点下建立一个临时顺序节点,此时名字会变成相似于:
你们看看下面的图:
客户端B由于是第二个来建立顺序节点的,因此zk内部会维护序号为"2"。
接着客户端B会走加锁判断逻辑,查询"my_lock"锁节点下的全部子节点,按序号顺序排列,此时他看到的相似于:
同时检查本身建立的顺序节点,是否是集合中的第一个?
明显不是啊,此时第一个是客户端A建立的那个顺序节点,序号为"01"的那个。因此加锁失败!
加锁失败了之后,客户端B就会经过ZK的API对他的顺序节点的上一个顺序节点加一个监听器。zk自然就能够实现对某个节点的监听。
若是你们还不知道zk的基本用法,能够百度查阅,很是的简单。客户端B的顺序节点是:
他的上一个顺序节点,不就是下面这个吗?
即客户端A建立的那个顺序节点!
因此,客户端B会对:
这个节点加一个监听器,监听这个节点是否被删除等变化!你们看下面的图。
接着,客户端A加锁以后,可能处理了一些代码逻辑,而后就会释放锁。那么,释放锁是个什么过程呢?
其实很简单,就是把本身在zk里建立的那个顺序节点,也就是:
这个节点给删除。
删除了那个节点以后,zk会负责通知监听这个节点的监听器,也就是客户端B以前加的那个监听器,说:兄弟,你监听的那个节点被删除了,有人释放了锁。
此时客户端B的监听器感知到了上一个顺序节点被删除,也就是排在他以前的某个客户端释放了锁。
此时,就会通知客户端B从新尝试去获取锁,也就是获取"my_lock"节点下的子节点集合,此时为:
集合里此时只有客户端B建立的惟一的一个顺序节点了!
而后呢,客户端B判断本身竟然是集合中的第一个顺序节点,bingo!能够加锁了!直接完成加锁,运行后续的业务代码便可,运行完了以后再次释放锁。
3、总结
其实若是有客户端C、客户端D等N个客户端争抢一个zk分布式锁,原理都是相似的。
- 你们都是上来直接建立一个锁节点下的一个接一个的临时顺序节点
- 若是本身不是第一个节点,就对本身上一个节点加监听器
- 只要上一个节点释放锁,本身就排到前面去了,至关因而一个排队机制。
并且用临时顺序节点的另一个用意就是,若是某个客户端建立临时顺序节点以后,不当心本身宕机了也不要紧,zk感知到那个客户端宕机,会自动删除对应的临时顺序节点,至关于自动释放锁,或者是自动取消本身的排队。
最后,我们来看下用Curator框架进行加锁和释放锁的一个过程:
其实用开源框架就是这点好,方便。这个Curator框架的zk分布式锁的加锁和释放锁的实现原理,其实就是上面咱们说的那样子。
可是若是你要手动实现一套那个代码的话。仍是有点麻烦的,要考虑到各类细节,异常处理等等。因此你们若是考虑用zk分布式锁,能够参考下本文的思路。
END
基于以上的一些zk的特性,咱们很容易得出使用zk实现分布式锁的落地方案:
-
使用zk的临时节点和有序节点,每一个线程获取锁就是在zk建立一个临时有序的节点,好比在/lock/目录下。
-
建立节点成功后,获取/lock目录下的全部临时节点,再判断当前线程建立的节点是不是全部的节点的序号最小的节点
-
若是当前线程建立的节点是全部节点序号最小的节点,则认为获取锁成功。
-
若是当前线程建立的节点不是全部节点序号最小的节点,则对节点序号的前一个节点添加一个事件监听。
好比当前线程获取到的节点序号为
/lock/003
,而后全部的节点列表为[/lock/001,/lock/002,/lock/003]
,则对/lock/002
这个节点添加一个事件监听器。
若是锁释放了,会唤醒下一个序号的节点,而后从新执行第3步,判断是否本身的节点序号是最小。
好比/lock/001
释放了,/lock/002
监听到时间,此时节点集合为[/lock/002,/lock/003]
,则/lock/002
为最小序号节点,获取到锁。
两种方案的优缺点比较
学完了两种分布式锁的实现方案以后,本节须要讨论的是redis和zk的实现方案中各自的优缺点。
对于redis的分布式锁而言,它有如下缺点:
-
它获取锁的方式简单粗暴,获取不到锁直接不断尝试获取锁,比较消耗性能。
-
redis分布式锁,其实须要本身不断去尝试获取锁,比较消耗性能。
对于zk分布式锁而言:
-
zookeeper天生设计定位就是分布式协调,强一致性。锁的模型健壮、简单易用、适合作分布式锁。
-
若是获取不到锁,只须要添加一个监听器就能够了,不用一直轮询,性能消耗较小。
可是zk也有其缺点:若是有较多的客户端频繁的申请加锁、释放锁,对于zk集群的压力会比较大。