摘要:分享牛原创,zookeeper使用,zookeeper锁在实际项目开发中还是很常用的,在这里我们介绍一下zookeeper分布式锁的使用,以及我们如何zookeeper分布式锁的原理。zookeeper节点理解。

zookeeper分布式锁有什么用呢?首先要明白锁是一个什么东西?举个通俗的例子,把门锁着了,外面的人进不去,里面的人可以随时出来,出来之后,还可以继续加锁。比如我们项目中,主要有供应商系统锁库存这种情况,锁库存的时候不能让其他的人去修改库存信息。这里就需要使用的时候加锁。当然了也可以使用数据库或者redis版本锁的概念,根据版本去区分到底如何锁库。

首先我们看一下zookeeper节点类型。

zookeeper节点类型分为以下四种:

1.1.1. 节点说明

  1. public enum CreateMode {
  2.  
  3. /**
  4. * The znode will not be automatically deleted upon client's disconnect.
  5. */
  6. PERSISTENT (0, false, false),
  7. /**
  8. * The znode will not be automatically deleted upon client's disconnect,
  9. * and its name will be appended with a monotonically increasing number.
  10. */
  11. PERSISTENT_SEQUENTIAL (2, false, true),
  12. /**
  13. * The znode will be deleted upon the client's disconnect.
  14. */
  15. EPHEMERAL (1, true, false),
  16. /**
  17. * The znode will be deleted upon the client's disconnect, and its name
  18. * will be appended with a monotonically increasing number.
  19. */
  20. EPHEMERAL_SEQUENTIAL (3, true, true);
  21. }

从持久化的层次划分:

1.持久化节点:不删除节点永远存在。

2.非持久节点,换言之就是临时节点,临时节点就是客户端连接的时候创建,客户端挂起的时候,临时节点自动删除。

从排序层次划分:

1.持久有序。

2.持久无序,

3.临时有序。

4.临时无序。

这里需要注意持久化节点可以创建子节点。非持久化节点不能创建子节点。这里可以自己去使用命令去测试。

非持久节点就是创建的时候存在,消失的时候,节点自动删除,所以我们利用这个特性,实现我们的需求,比如,我可以程序启动的时候在指定的持久节点,创建临时节点,当程序挂掉的时候,临时节点消失,我们可以一直监控指定父节点中的子节点集合,就可以监控程序的健康状态。

1.1.2. zookeeper 分布式锁实现

接下来,在上面理解节点的基础之上,我们可以去实现zookeeper 分布式锁。具体怎么实现呢?思路如下:

我们可以创建一个持久节点,在程序中我们每次创建临时子节点,然后我们遍历持久节点下面的子节点,因为临时节点我们设置的时候是有序的。所以我们可以加锁的时候,创建了一个临时有序节点,当我们加锁完成自己的业务之后,释放锁,然后,这个删除临时节点,所以设计的核心点就是:

1.创建父节点。持久节点

2.创建有序的临时子节点。

3.删除临时节点,当业务完成的时候。

4.每次需要判断自身的临时节点,是否是最小的。为什么要判断是最小的呢?因为不是最小的话,说明前面还有一些节点在加锁执行中,所以我们这个节点不能加锁执行。

5.怎么让自身节点监听执行呢?因为如果自身节点是最小的,可以直接执行,如果不是最小的。要监听前面的节点是否已经删除,如果其他的前面的节点都删除了。则自己就可以加锁执行业务代码了。

下面开始书写我们的代码吧?

  1. package com.shareniu.zkTest;
  2. import java.io.IOException;
  3. import java.util.Collections;
  4. import java.util.List;
  5. import java.util.concurrent.CountDownLatch;
  6.  
  7. import org.apache.zookeeper.CreateMode;
  8. import org.apache.zookeeper.KeeperException;
  9. import org.apache.zookeeper.WatchedEvent;
  10. import org.apache.zookeeper.Watcher;
  11. import org.apache.zookeeper.Watcher.Event;
  12. import org.apache.zookeeper.Watcher.Event.EventType;
  13. import org.apache.zookeeper.Watcher.Event.KeeperState;
  14. import org.apache.zookeeper.ZooDefs.Ids;
  15. import org.apache.zookeeper.ZooKeeper;
  16. import org.apache.zookeeper.data.Stat;
  17.  
  18. public class ShareniuDistributedLock implements Watcher {
  19. private int threadId;
  20. // 主要区分线程
  21. private static String PREFIX_OF_THREAD = null;
  22. // 子节点的前缀
  23. private static final String EPHEMERAL_SEQUENTIAL_PATH = "/shareniuLock/sub";
  24. // 父节点
  25. private static final String PARENT_PATH = "/shareniuLock";
  26. protected static final String CONNECTION_STRING = "101.201.xx.xx:2181";
  27. protected static final int SESSION_TIMEOUT = 10000;
  28. //开启的线程的数量
  29. private static final int THREAD_NUM = 5;
  30. // 创建临时节点自身
  31. private String selfPath;
  32. // zk连接对象
  33. public ShareniuDistributedLock(int threadId) {
  34. this.threadId = threadId;
  35. PREFIX_OF_THREAD = "【第"+threadId+"个线程】";
  36. }
  37. private ZooKeeper zooKeeper = null;
  38. private CountDownLatch cdl = new CountDownLatch(1);
  39. private static final CountDownLatch threadSemaphore = new CountDownLatch(THREAD_NUM);
  40. private String waitPath;
  41. public static void main(String[] args) {
  42. for(int i=0; i < THREAD_NUM; i++){
  43. final int threadId = i+1;
  44. new Thread(){
  45. @Override
  46. public void run() {
  47. try{
  48. ShareniuDistributedLock dc = new ShareniuDistributedLock(threadId);
  49. dc.createConnection(CONNECTION_STRING, SESSION_TIMEOUT);
  50. //GROUP_PATH不存在的话,由一个线程创建即可;
  51. synchronized (threadSemaphore){
  52. dc.createPath(PARENT_PATH, "该节点由线程" + threadId + "创建", true);
  53. }
  54. dc.getLock();
  55. } catch (Exception e){
  56. e.printStackTrace();
  57. }
  58. }
  59. }.start();
  60. }
  61. try {
  62. threadSemaphore.await();
  63. System.out.println("所有线程运行结束!");
  64. } catch (InterruptedException e) {
  65. e.printStackTrace();
  66. }
  67. }
  68. /**
  69. * 实现监听器中的方法拿到WatchedEvent对象
  70. */
  71. public void process(WatchedEvent event) {
  72. if(event == null){
  73. return;
  74. }
  75. Event.KeeperState keeperState = event.getState();
  76. Event.EventType eventType = event.getType();
  77. if ( Event.KeeperState.SyncConnected == keeperState) {
  78. if ( Event.EventType.None == eventType ) {
  79. System.out.println( PREFIX_OF_THREAD + "成功连接上ZK服务器" );
  80. cdl.countDown();
  81. }else if (event.getType() == Event.EventType.NodeDeleted && event.getPath().equals(waitPath)) {
  82. try {
  83. if(checkMinPathOfChilde()){
  84. getLockByShelf();
  85. }
  86. } catch (KeeperException e) {
  87. e.printStackTrace();
  88. } catch (InterruptedException e) {
  89. e.printStackTrace();
  90. }
  91. }
  92. }else if ( Event.KeeperState.Disconnected == keeperState ) {
  93. System.out.println( PREFIX_OF_THREAD + "与ZK服务器断开连接" );
  94. } else if ( Event.KeeperState.AuthFailed == keeperState ) {
  95. System.out.println( PREFIX_OF_THREAD + "权限检查失败" );
  96. } else if ( Event.KeeperState.Expired == keeperState ) {
  97. System.out.println( PREFIX_OF_THREAD + "会话失效" );
  98. }
  99. }
  100.  
  101. /**
  102. * 关闭ZK连接
  103. */
  104. public void releaseConnection() {
  105. if (this.zooKeeper != null) {
  106. try {
  107. this.zooKeeper.close();
  108. } catch (InterruptedException e) {
  109. }
  110. }
  111. System.out.println(PREFIX_OF_THREAD + "释放连接");
  112. }
  113.  
  114. /***
  115. * 创建连接
  116. *
  117. * @param connectString
  118. * 连接的服务器字符串
  119. * @param sessionTimeout
  120. * 超时时间
  121. * @throws IOException
  122. * @throws InterruptedException
  123. */
  124. public void createConnection(String connectString, int sessionTimeout)
  125. throws IOException, InterruptedException {
  126. zooKeeper = new ZooKeeper(connectString, sessionTimeout, this);
  127. System.out.println("打开连接........");
  128. // 因为打开连接只需要一次,这里为了防止并发,使用的CountDownLatch对象
  129. // 程序一直等待,直到cdl.countDown();方法的调用
  130. cdl.await();
  131. }
  132.  
  133. /**
  134. * 创建节点
  135. *
  136. * @param path
  137. * 路径
  138. * @param data
  139. * 内容
  140. * @param needWatch
  141. * @return
  142. * @throws KeeperException
  143. * @throws InterruptedException
  144. */
  145. public boolean createPath(String path, String data, boolean needWatch)
  146. throws KeeperException, InterruptedException {
  147. // 判断节点是否存在存在就不要创建了。
  148. Stat exists = zooKeeper.exists(path, needWatch);
  149. if (exists == null) {
  150. // 节点不存在
  151. System.out.println(PREFIX_OF_THREAD
  152. + "节点创建成功, Path: "
  153. + this.zooKeeper.create(path, data.getBytes(),
  154. Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)
  155. + ", content: " + data);
  156. }
  157. return true;
  158. }
  159.  
  160. /**
  161. * 获取锁
  162. *
  163. * @throws KeeperException
  164. * @throws InterruptedException
  165. */
  166. private void getLock() throws KeeperException, InterruptedException {
  167. // 创建临时节点
  168. selfPath = zooKeeper.create(EPHEMERAL_SEQUENTIAL_PATH, null,
  169. Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
  170. System.out.println("创建临时节点:" + selfPath);
  171. if (checkMinPathOfChilde()) {
  172. getLockByShelf();
  173. }
  174. }
  175.  
  176. /**
  177. * 获取到锁了
  178. *
  179. * @throws InterruptedException
  180. * @throws KeeperException
  181. */
  182. private void getLockByShelf() throws KeeperException, InterruptedException {
  183. // 获取到锁了 开始 执行代码 删除节点 释放连接
  184. if (zooKeeper.exists(this.selfPath, false) == null) {
  185. System.out.println(PREFIX_OF_THREAD + "本节点已不在了...");
  186. return;
  187. } else {
  188. // 节点存在
  189. System.out.println(PREFIX_OF_THREAD + "获取锁成功....");
  190. // 休息一下
  191. Thread.sleep(1000);
  192. System.out.println(PREFIX_OF_THREAD + "删除临时节点:" + selfPath);
  193. // 删除的时候,版本是-1就是所有的都删除
  194. zooKeeper.delete(this.selfPath, -1);
  195. releaseConnection();
  196. // 释放锁 其他的程序可以 继续打开连接
  197. cdl.countDown();
  198. }
  199. }
  200.  
  201. /**
  202. * 获取锁
  203. *
  204. * @return
  205. * @throws InterruptedException
  206. * @throws KeeperException
  207. */
  208. private boolean checkMinPathOfChilde() throws KeeperException,
  209. InterruptedException {
  210. // 获取父节点中的所有子节点
  211. List<String> subNodes = zooKeeper.getChildren(PARENT_PATH, false);
  212. // 临时节点是有序的,那就排序找最小的吧
  213. Collections.sort(subNodes);
  214. int index = subNodes
  215. .indexOf(selfPath.substring(PARENT_PATH.length() + 1));
  216. switch (index) {
  217. case -1: {
  218. System.out.println(PREFIX_OF_THREAD + "节点已不在了..." + selfPath);
  219. return false;
  220. }
  221. case 0: {
  222. System.out.println(PREFIX_OF_THREAD + "自己可以获取锁执行代码了" + selfPath);
  223. return true;
  224. }
  225. default: {
  226. this.waitPath = PARENT_PATH + "/" + subNodes.get(index - 1);
  227. System.out.println(PREFIX_OF_THREAD + "前面的节点" + waitPath);
  228. try {
  229. zooKeeper.getData(waitPath, true, new Stat());
  230. return false;
  231. } catch (KeeperException e) {
  232. if (zooKeeper.exists(waitPath, false) == null) {
  233. System.out.println(PREFIX_OF_THREAD + "本节点前面的节点:"
  234. + waitPath + "");
  235. // 递归找吧
  236. return checkMinPathOfChilde();
  237. } else {
  238. throw e;
  239. }
  240. }
  241. }
  242. }
  243. }
  244. }

1.1.3. 程序的输出

程序的输出如下:

打开连接........

打开连接........

打开连接........

打开连接........

打开连接........

【第5个线程】成功连接上ZK服务器

【第5个线程】成功连接上ZK服务器

【第5个线程】成功连接上ZK服务器

【第5个线程】成功连接上ZK服务器

【第5个线程】成功连接上ZK服务器

创建临时节点:/shareniuLock/sub0000000001

【第5个线程】自己可以获取锁执行代码了/shareniuLock/sub0000000001

创建临时节点:/shareniuLock/sub0000000002

【第5个线程】获取锁成功....

【第5个线程】前面的节点/shareniuLock/sub0000000001

创建临时节点:/shareniuLock/sub0000000003

创建临时节点:/shareniuLock/sub0000000004

【第5个线程】前面的节点/shareniuLock/sub0000000002

【第5个线程】前面的节点/shareniuLock/sub0000000003

创建临时节点:/shareniuLock/sub0000000005

【第5个线程】前面的节点/shareniuLock/sub0000000004

【第5个线程】删除临时节点:/shareniuLock/sub0000000001

【第5个线程】释放连接

【第5个线程】自己可以获取锁执行代码了/shareniuLock/sub0000000002

【第5个线程】获取锁成功....

【第5个线程】删除临时节点:/shareniuLock/sub0000000002

【第5个线程】释放连接

【第5个线程】自己可以获取锁执行代码了/shareniuLock/sub0000000003

【第5个线程】获取锁成功....

【第5个线程】删除临时节点:/shareniuLock/sub0000000003

【第5个线程】释放连接

【第5个线程】自己可以获取锁执行代码了/shareniuLock/sub0000000004

【第5个线程】获取锁成功....

【第5个线程】删除临时节点:/shareniuLock/sub0000000004

【第5个线程】释放连接

【第5个线程】自己可以获取锁执行代码了/shareniuLock/sub0000000005

【第5个线程】获取锁成功....

【第5个线程】删除临时节点:/shareniuLock/sub0000000005

【第5个线程】释放连接

1.1.4. zk节点的查看

下面我们看一下zk的节点,

在此证明,节点是有序的,释放锁的时候,会删除临时节点。

分享牛原创(尊重原创 转载对的时候第一行请注明,转载出处来自分享牛http://blog.csdn.net/qq_30739519)

zookeeper分布式锁的更多相关文章

  1. Curator Zookeeper分布式锁

    Curator Zookeeper分布式锁 pom.xml中添加如下配置 <!-- https://mvnrepository.com/artifact/org.apache.curator/c ...

  2. ZooKeeper 分布式锁实现

    1 场景描述 在分布式应用, 往往存在多个进程提供同一服务. 这些进程有可能在相同的机器上, 也有可能分布在不同的机器上. 如果这些进程共享了一些资源, 可能就需要分布式锁来锁定对这些资源的访问. 2 ...

  3. ZooKeeper分布式锁浅谈(一)

    一.概述 清明节的时候写了一篇分布式锁概述,里面介绍了分布式锁实现的几种方式,其实那时候我一直沉迷于使用redis的悲观锁和乐观锁来实现分布式锁,直到一个血案的引发才让我重新认识了redis分布式锁的 ...

  4. [转载] zookeeper 分布式锁服务

    转载自http://www.cnblogs.com/shanyou/archive/2012/09/22/2697818.html 分布式锁服务在大家的项目中或许用的不多,因为大家都把排他放在数据库那 ...

  5. 跟着大神学zookeeper分布式锁实现-----来自Ruthless

    前几天分享了@Ruthless大神的Redis锁,发现和大家都学习了很多东西.因为分布式锁里面,最好的实现是zookeeper的分布式锁.所以在这里把实现方式和大家分享一下. zookeeper分布式 ...

  6. 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁

    首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...

  7. zookeeper 分布式锁原理

    zookeeper 分布式锁原理: 1 大家也许都很熟悉了多个线程或者多个进程间的共享锁的实现方式了,但是在分布式场景中我们会面临多个Server之间的锁的问题,实现的复杂度比较高.利用基于googl ...

  8. 分布式锁(一) Zookeeper分布式锁

    什么是Zookeeper? Zookeeper(业界简称zk)是一种提供配置管理.分布式协同以及命名的中心化服务,这些提供的功能都是分布式系统中非常底层且必不可少的基本功能,但是如果自己实现这些功能而 ...

  9. ZooKeeper 分布式锁

    在Redis分布式锁一文中, 作者介绍了如何使用Redis开发分布式锁. Redis分布式锁具有轻量高吞吐量的特点,但是一致性保证较弱.我们可以使用Zookeeper开发分布式锁,来满足对高一致性的要 ...

随机推荐

  1. Python中if __name__ == "__main__": 的理解

    1.在很多python脚本中在最后的部分会执行一个判断语句if __name__ == "__main__:",之后还可能会有一些执行语句.那添加这个判断的目的何在? 在pytho ...

  2. c#之监控文件结构

    如果需要知道修改文件或目录的时间,可以通过FileSystemWatcher类,这个类提供了一下应用程序可以捕获的事件,应用程序可以对事件作出响应. 使用FileSystemWatcher非常简单,首 ...

  3. C#之Message(转)

    一.消息概述 Windows下应用程序的执行是通过消息驱动的.消息是整个应用程序的工作引擎,我们需要理解掌握我们使用的编程语言是如何封装消息的原理. 什么是消息(Message) 消息就是通知和命令. ...

  4. .NET Core2.0+MVC 用session,cookie实现的sso单点登录

    博主刚接触.NET Core2.0,想做一个单点登录的demo,所以参考了一些资料,这里给上链接: 1.http://www.cnblogs.com/baibaomen/p/sso-sequence- ...

  5. IOS开发- 访问通讯录,并将通讯录中姓名-头像-手机号码 发给服务器

    现在很多软件都会访问通讯录,并将通讯录的信息取得,发给服务器,然后服务器会返回相应电话号码的用户是否注册. 现在分享一下前两步,访问通讯录并处理通讯录的信息 1.导入框架 #import <Ad ...

  6. [AHOI 2009]chess 中国象棋

    Description 题库链接 给你一张 \(N\times M\) 的棋盘.要求每行每列最多放两个棋子,问总方案数. \(1\leq N,M\leq 100\) Solution 记 \(f_{i ...

  7. codefroces 297E Mystic Carvings

    problem:一个圆上依次有1~2*n的数字.每个数字都有且只有另一个数字与他相连.选出三条线,使得每条线的两端之间隔的最少点(只包括被选择的6个点)的个数相等.输入输出格式输入格式: The fi ...

  8. 【The Time Traveller's Wife】

    After reading The Time Traveller's Wife:      It's a tragedy,I think.But it's mixed with hope.Henry ...

  9. hdu 1166 线段树(sum+单点修改)

    敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

  10. Linux内核异常处理体系结构详解(一)【转】

    转自:http://www.techbulo.com/1841.html 2015年11月30日 ⁄ 基础知识 ⁄ 共 6653字 ⁄ 字号 小 中 大 ⁄ Linux内核异常处理体系结构详解(一)已 ...