关于分布式锁,在互联网行业的使用场景仍是比较多的,好比电商的库存扣减,秒杀活动,集群定时任务执行等须要进程互斥的场景。而实现分布式锁的手段也不少,你们比较常见的就是redis跟zookeeper,今天咱们主要介绍的是基于zookeeper实现的分布式锁。java
这篇文章主要借用Curator框架对zk分布式锁的实现思路,你们理解了之后彻底能够本身手动实现一遍,可是在工做中仍是建议使用成熟的开源框架,不少坑别人已经帮咱们踩好了,除非万不得已,须要高度定制符合本身项目的需求的时候,才开始自行封装吧。redis
既然是基于zookeeper的分布式锁,首先确定要对这个zookeeper有必定了解,这里就不过多的进行讲解,只对其跟分布式锁有关联的特性作一个简单的介绍,更多详细的功能特性你们能够参阅官方文档。api
zookeeper维护着相似文件系统的数据结构,它总共有四种类型的节点性能优化
好,当咱们简单了解了zk的节点类型之后,如今正式的分析Curator分布式锁的实现原理。这里咱们定义了一个“/curator_lock”锁节点用来存放相关客户端建立的临时顺序节点。bash
假设两个客户端ClientA跟ClientB同时去争夺一个锁,此时ClientA先行一步得到了锁,那么它将会在咱们的zk上建立一个“/curator_lock/xxxxx-0000000000”的临时顺序节点。微信
接着它会拿到“/curator_lock/”锁节点下的全部子节点,由于这些节点是有序的,这时候会判断它所建立的节点是否排在第一位(也就是序号最小),因为ClientA是第一个建立节点的的客户端,必然是排在第一位,因此它也就拿到了锁。markdown
[zk: localhost:2182(CONNECTED) 4] ls /curator_lock [_c_f3f38067-8bff-47ef-9628-e638cfaad77e-lock-0000000000] 复制代码
这个时候ClientB也来了,按照一样的步骤,先是在“/curator_lock/”下建立一个临时顺序节点“/curator_lock/xxxxx-0000000001”,接着也是得到该节点下的全部子节点信息,并比对本身生成的节点序号是否最小,因为此时序号最小的节点是ClientA建立的,而且还没释放掉,因此ClientB本身就拿不到锁。数据结构
[zk: localhost:2182(CONNECTED) 4] ls /curator_lock [_c_2a8198e4-2039-4a3c-8606-39c65790d637-lock-0000000001, _c_f3f38067-8bff-47ef-9628-e638cfaad77e-lock-0000000000] 复制代码
既然ClientB拿不到锁,也不会放弃,它会对本身的前一个节点加上监听器(zk提供的api实现),只要监听到前一个节点被删除了,也就是释放了锁,就会立刻从新执行获取锁的操做。框架
当后面的ClientC,ClientD...过来的时候也是如此,变化的只是节点上的编号,它会根据Client链接的数量而不断增长。分布式
可能你们还会担忧,万一个人获取到锁的客户端宕机了怎么办,会不会不释放锁?其实上面已经解答了这个问题,因为Curator使用的是临时顺序节点来实现的分布式锁,只要客户端与zk链接断开,该节点也就消失了,至关于释放了锁。
下面代码展现了Curator的基本使用方法,仅做为参考实例,请勿在生产环境使用的这么随意。
CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2182", 5000,10000, new ExponentialBackoffRetry(1000, 3)); client.start(); InterProcessMutex interProcessMutex = new InterProcessMutex(client, "/curator_lock"); //加锁 interProcessMutex.acquire(); //业务逻辑 //释放锁 interProcessMutex.release(); client.close(); 复制代码
咱们在搞懂了原理以后,就能够抛弃Curator,本身动手实现一个分布式锁了,相信你们实现基本的功能都是没问题的,可是要作到生产级别,可能仍是要在细节上下功夫,好比说一些异常处理,性能优化等因素。