同样的,我直接基于Curator这个比较常用的开源框架,讲一下这个框架对ZooKeeper(以下简称zk)分布式锁的实现。一般除了大公司自己封装分布式锁框架外,建议大家使用这些开源框架来实现分布式锁。这是一种更快、更省事的方式。2.ZooKeeper分布式锁机制接下来我们看一下多客户端获取和释放zk分布式锁的整个过程以及背后的原理。首先,我们来看下图。如果两个客户端要竞争zk上的一个分布式锁,会发生什么?如果你还不太了解zk,建议先百度一下,快速了解一些基本概念,比如zk有哪些节点类型等等。见上图。zk中有一把锁,这把锁就是zk上的一个节点。那么,两个client都要获取这个锁,具体怎么获取呢?假设客户端A迈出了第一步,发起了向zk添加分布式锁的请求。这个锁请求使用了zk中一个特殊的概念,叫做“临时时序节点”。简单点说,就是在锁节点“my_lock”的正下方创建一个时序节点。这个时序节点有一个由zk自己维护的节点序号。比如第一个客户端来创建顺序节点,zk内部会命名为:xxx-000001。然后第二个client来创建一个时序节点,zk可能会命名为:xxx-000002。请注意,最后一个数字从1开始按顺序递增。zk将保持此顺序。所以这个时候,如果客户端A先发起请求,就会产生一个顺序节点。看下图,Curator框架大概是这样的:你看,客户端A发起锁请求。将在您要锁定的节点下创建一个临时顺序节点。这一大堆长名字是Curator框架自己生成的。然后,最后一个数字是“1”。请注意,因为客户端A是第一个发起请求的,所以为其创建的时序节点序号为“1”。然后客户端A创建一个顺序节点。还没完,他会去查锁节点“my_lock”下的所有子节点,这些子节点是按照序号排序的,这时候他大概会得到这样一个集合:那么客户端A会去到一个key审判的意思是:唉!大哥,在这个集合中,我创建的时序节点排在第一位吗?如果是这样,那我可以锁定它!因为很明显我是第一个创建顺序节点的人,所以我是第一个尝试加分布式锁的人!答对了!锁定成功!看看下图,再直观感受一下整个过程。那么假设客户端A已经加锁完毕,客户端B要加锁。这时,他会做同样的事情:先在锁节点“my_lock”下创建一个临时序列节点。名字会变成类似:看下图:客户端B是第二个创建序列节点的,所以zk内部维护序列号为“2”。然后clientB会按照锁判断逻辑,查询“my_lock”锁节点下的所有子节点,并按照序号顺序排列。这时,他看到的类似于:同时查看自己创建的时序节点是否在集合First中?显然不是,此时第一个就是客户端A创建的序号为“01”的时序节点。所以锁失败了!锁失效后,客户端B会通过ZK的API为其最后一个时序节点添加监听。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分布式锁,可以参考这篇文章的思路。
