我们常说的锁是单进程多线程锁,在多线程并发编程中,用于线程之间的数据同步,保护共享资源的访问。而分布式锁,指在分布式环境下,保护跨进程、跨主机、跨网络的共享资源,实现互斥访问,保证一致性。

架构图:

分布式锁获取思路
a、在获取分布式锁的时候在locker节点下创建临时顺序节点,释放锁的时候删除该临时节点。

b、客户端调用createNode方法在locker下创建临时顺序节点,然后调用getChildren(“locker”)来获取locker下面的所有子节点,注意此时不用设置任何Watcher。

c、客户端获取到所有的子节点path之后,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁。

d、如果发现自己创建的节点并非locker所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那个节点,然后对其调用exist()方法,同时对其注册事件监听器。

e、之后,让这个被关注的节点删除,则客户端的Watcher会收到相应通知,此时再次判断自己创建的节点是否是locker子节点中序号最小的,如果是则获取到了锁,如果不是则重复以上步骤继续获取到比自己小的一个节点并注册监听。

实现代码:

  1. import org.I0Itec.zkclient.IZkDataListener;
  2. import org.I0Itec.zkclient.ZkClient;
  3. import org.I0Itec.zkclient.exception.ZkNoNodeException;
  4.  
  5. import java.util.Collections;
  6. import java.util.Comparator;
  7. import java.util.List;
  8. import java.util.concurrent.CountDownLatch;
  9. import java.util.concurrent.TimeUnit;
  10.  
  11. public class BaseDistributedLock {
  12.  
  13. private final ZkClientExt client;
  14. private final String path;
  15. private final String basePath;
  16. private final String lockName;
  17. private static final Integer MAX_RETRY_COUNT = 10;
  18.  
  19. public BaseDistributedLock(ZkClientExt client, String path, String lockName){
  20.  
  21. this.client = client;
  22. this.basePath = path;
  23. this.path = path.concat("/").concat(lockName);
  24. this.lockName = lockName;
  25.  
  26. }
  27.  
  28. // 删除成功获取锁之后所创建的那个顺序节点
  29. private void deleteOurPath(String ourPath) throws Exception{
  30. client.delete(ourPath);
  31. }
  32.  
  33. // 创建临时顺序节点
  34. private String createLockNode(ZkClient client, String path) throws Exception{
  35. return client.createEphemeralSequential(path, null);
  36. }
  37.  
  38. // 等待比自己次小的顺序节点的删除
  39. private boolean waitToLock(long startMillis, Long millisToWait, String ourPath) throws Exception{
  40.  
  41. boolean haveTheLock = false;
  42. boolean doDelete = false;
  43.  
  44. try {
  45.  
  46. while ( !haveTheLock ) {
  47. // 获取/locker下的经过排序的子节点列表
  48. List<String> children = getSortedChildren();
  49.  
  50. // 获取刚才自己创建的那个顺序节点名
  51. String sequenceNodeName = ourPath.substring(basePath.length()+1);
  52.  
  53. // 判断自己排第几个
  54. int ourIndex = children.indexOf(sequenceNodeName);
  55. if (ourIndex < 0){ // 网络抖动,获取到的子节点列表里可能已经没有自己了
  56. throw new ZkNoNodeException("节点没有找到: " + sequenceNodeName);
  57. }
  58.  
  59. // 如果是第一个,代表自己已经获得了锁
  60. boolean isGetTheLock = ourIndex == 0;
  61.  
  62. // 如果自己没有获得锁,则要watch比我们次小的那个节点
  63. String pathToWatch = isGetTheLock ? null : children.get(ourIndex - 1);
  64.  
  65. if ( isGetTheLock ){
  66. haveTheLock = true;
  67.  
  68. } else {
  69.  
  70. // 订阅比自己次小顺序节点的删除事件
  71. String previousSequencePath = basePath .concat( "/" ) .concat( pathToWatch );
  72. final CountDownLatch latch = new CountDownLatch(1);
  73. final IZkDataListener previousListener = new IZkDataListener() {
  74.  
  75. public void handleDataDeleted(String dataPath) throws Exception {
  76. latch.countDown(); // 删除后结束latch上的await
  77. }
  78.  
  79. public void handleDataChange(String dataPath, Object data) throws Exception {
  80. // ignore
  81. }
  82. };
  83.  
  84. try {
  85. //订阅次小顺序节点的删除事件,如果节点不存在会出现异常
  86. client.subscribeDataChanges(previousSequencePath, previousListener);
  87.  
  88. if ( millisToWait != null ) {
  89. millisToWait -= (System.currentTimeMillis() - startMillis);
  90. startMillis = System.currentTimeMillis();
  91. if ( millisToWait <= 0 ) {
  92. doDelete = true; // timed out - delete our node
  93. break;
  94. }
  95.  
  96. latch.await(millisToWait, TimeUnit.MICROSECONDS); // 在latch上await
  97. } else {
  98. latch.await(); // 在latch上await
  99. }
  100.  
  101. // 结束latch上的等待后,继续while重新来过判断自己是否第一个顺序节点
  102. }
  103. catch ( ZkNoNodeException e ) {
  104. //ignore
  105. } finally {
  106. client.unsubscribeDataChanges(previousSequencePath, previousListener);
  107. }
  108.  
  109. }
  110. }
  111. }
  112. catch ( Exception e ) {
  113. //发生异常需要删除节点
  114. doDelete = true;
  115. throw e;
  116. } finally {
  117. //如果需要删除节点
  118. if ( doDelete ) {
  119. deleteOurPath(ourPath);
  120. }
  121. }
  122. return haveTheLock;
  123. }
  124.  
  125. private String getLockNodeNumber(String str, String lockName) {
  126. int index = str.lastIndexOf(lockName);
  127. if ( index >= 0 ) {
  128. index += lockName.length();
  129. return index <= str.length() ? str.substring(index) : "";
  130. }
  131. return str;
  132. }
  133.  
  134. // 获取/locker下的经过排序的子节点列表
  135. List<String> getSortedChildren() throws Exception {
  136. try{
  137.  
  138. List<String> children = client.getChildren(basePath);
  139. Collections.sort(
  140. children, new Comparator<String>() {
  141. public int compare(String lhs, String rhs) {
  142. return getLockNodeNumber(lhs, lockName).compareTo(getLockNodeNumber(rhs, lockName));
  143. }
  144. }
  145. );
  146. return children;
  147.  
  148. } catch (ZkNoNodeException e){
  149. client.createPersistent(basePath, true);
  150. return getSortedChildren();
  151. }
  152. }
  153.  
  154. protected void releaseLock(String lockPath) throws Exception{
  155. deleteOurPath(lockPath);
  156. }
  157.  
  158. protected String attemptLock(long time, TimeUnit unit) throws Exception {
  159.  
  160. final long startMillis = System.currentTimeMillis();
  161. final Long millisToWait = (unit != null) ? unit.toMillis(time) : null;
  162.  
  163. String ourPath = null;
  164. boolean hasTheLock = false;
  165. boolean isDone = false;
  166. int retryCount = 0;
  167.  
  168. //网络闪断需要重试一试
  169. while ( !isDone ) {
  170. isDone = true;
  171.  
  172. try {
  173. // 在/locker下创建临时的顺序节点
  174. ourPath = createLockNode(client, path);
  175. // 判断自己是否获得了锁,如果没有获得那么等待直到获得锁或者超时
  176. hasTheLock = waitToLock(startMillis, millisToWait, ourPath);
  177. } catch ( ZkNoNodeException e ) { // 捕获这个异常
  178. if ( retryCount++ < MAX_RETRY_COUNT ) { // 重试指定次数
  179. isDone = false;
  180. } else {
  181. throw e;
  182. }
  183. }
  184. }
  185. if ( hasTheLock ) {
  186. return ourPath;
  187. }
  188.  
  189. return null;
  190. }
  191.  
  192. }
  1. import java.util.concurrent.TimeUnit;
  2.  
  3. public interface DistributedLock {
  4.  
  5. /*
  6. * 获取锁,如果没有得到就等待
  7. */
  8. public void acquire() throws Exception;
  9.  
  10. /*
  11. * 获取锁,直到超时
  12. */
  13. public boolean acquire(long time, TimeUnit unit) throws Exception;
  14.  
  15. /*
  16. * 释放锁
  17. */
  18. public void release() throws Exception;
  19.  
  20. }
  1. import java.io.IOException;
  2. import java.util.concurrent.TimeUnit;
  3.  
  4. public class SimpleDistributedLockMutex extends BaseDistributedLock implements
  5. DistributedLock {
  6.  
  7. //锁名称前缀,成功创建的顺序节点如lock-0000000000,lock-0000000001,...
  8. private static final String LOCK_NAME = "lock-";
  9.  
  10. // zookeeper中locker节点的路径
  11. private final String basePath;
  12.  
  13. // 获取锁以后自己创建的那个顺序节点的路径
  14. private String ourLockPath;
  15.  
  16. private boolean internalLock(long time, TimeUnit unit) throws Exception {
  17.  
  18. ourLockPath = attemptLock(time, unit);
  19. return ourLockPath != null;
  20.  
  21. }
  22.  
  23. public SimpleDistributedLockMutex(ZkClientExt client, String basePath){
  24.  
  25. super(client,basePath,LOCK_NAME);
  26. this.basePath = basePath;
  27.  
  28. }
  29.  
  30. // 获取锁
  31. public void acquire() throws Exception {
  32. if ( !internalLock(-1, null) ) {
  33. throw new IOException("连接丢失!在路径:'"+basePath+"'下不能获取锁!");
  34. }
  35. }
  36.  
  37. // 获取锁,可以超时
  38. public boolean acquire(long time, TimeUnit unit) throws Exception {
  39.  
  40. return internalLock(time, unit);
  41. }
  42.  
  43. // 释放锁
  44. public void release() throws Exception {
  45.  
  46. releaseLock(ourLockPath);
  47. }
  48.  
  49. }
  1. import org.I0Itec.zkclient.serialize.BytesPushThroughSerializer;
  2.  
  3. public class TestDistributedLock {
  4.  
  5. public static void main(String[] args) {
  6.  
  7. final ZkClientExt zkClientExt1 = new ZkClientExt("192.168.1.105:2181", 5000, 5000, new BytesPushThroughSerializer());
  8. final SimpleDistributedLockMutex mutex1 = new SimpleDistributedLockMutex(zkClientExt1, "/Mutex");
  9.  
  10. final ZkClientExt zkClientExt2 = new ZkClientExt("192.168.1.105:2181", 5000, 5000, new BytesPushThroughSerializer());
  11. final SimpleDistributedLockMutex mutex2 = new SimpleDistributedLockMutex(zkClientExt2, "/Mutex");
  12.  
  13. try {
  14. mutex1.acquire();
  15. System.out.println("Client1 locked");
  16. Thread client2Thd = new Thread(new Runnable() {
  17.  
  18. public void run() {
  19. try {
  20. mutex2.acquire();
  21. System.out.println("Client2 locked");
  22. mutex2.release();
  23. System.out.println("Client2 released lock");
  24.  
  25. } catch (Exception e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. });
  30. client2Thd.start();
  31. Thread.sleep(5000);
  32. mutex1.release();
  33. System.out.println("Client1 released lock");
  34.  
  35. client2Thd.join();
  36.  
  37. } catch (Exception e) {
  38.  
  39. e.printStackTrace();
  40. }
  41.  
  42. }
  43.  
  44. }
  1. import org.I0Itec.zkclient.ZkClient;
  2. import org.I0Itec.zkclient.serialize.ZkSerializer;
  3. import org.apache.zookeeper.data.Stat;
  4.  
  5. import java.util.concurrent.Callable;
  6.  
  7. public class ZkClientExt extends ZkClient {
  8.  
  9. public ZkClientExt(String zkServers, int sessionTimeout, int connectionTimeout, ZkSerializer zkSerializer) {
  10. super(zkServers, sessionTimeout, connectionTimeout, zkSerializer);
  11. }
  12.  
  13. @Override
  14. public void watchForData(final String path) {
  15. retryUntilConnected(new Callable<Object>() {
  16.  
  17. public Object call() throws Exception {
  18. Stat stat = new Stat();
  19. _connection.readData(path, stat, true);
  20. return null;
  21. }
  22.  
  23. });
  24. }
  25.  
  26. }

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

  1. zookeeper实现分布式锁服务

    A distributed lock base on zookeeper. zookeeper是hadoop下面的一个子项目, 用来协调跟hadoop相关的一些分布式的框架, 如hadoop, hiv ...

  2. [ZooKeeper.net] 3 ZooKeeper的分布式锁

    基于ZooKeeper的分布式锁 ZooKeeper 里实现分布式锁的基本逻辑: 1.zookeeper中创建一个根节点(Locks),用于后续各个客户端的锁操作. 2.想要获取锁的client都在L ...

  3. 基于 Zookeeper 的分布式锁实现

    1. 背景 最近在学习 Zookeeper,在刚开始接触 Zookeeper 的时候,完全不知道 Zookeeper 有什么用.且很多资料都是将 Zookeeper 描述成一个“类 Unix/Linu ...

  4. zookeeper的分布式锁

    实现分布式锁目前有三种流行方案,分别为基于数据库.Redis.Zookeeper的方案,其中前两种方案网络上有很多资料可以参考,本文不做展开.我们来看下使用Zookeeper如何实现分布式锁. 什么是 ...

  5. zookeeper 实现分布式锁安全用法

    zookeeper 实现分布式锁安全用法 标签: zookeeper sessionExpire connectionLoss 分布式锁 背景 ConnectionLoss 链接丢失 SessionE ...

  6. 基于Zookeeper的分布式锁

    实现分布式锁目前有三种流行方案,分别为基于数据库.Redis.Zookeeper的方案,其中前两种方案网络上有很多资料可以参考,本文不做展开.我们来看下使用Zookeeper如何实现分布式锁. 什么是 ...

  7. 转载 [ZooKeeper.net] 3 ZooKeeper的分布式锁

    [ZooKeeper.net] 3 ZooKeeper的分布式锁   基于ZooKeeper的分布式锁  源码分享:http://pan.baidu.com/s/1miQCDKk ZooKeeper ...

  8. Redis与Zookeeper实现分布式锁的区别

    Redis实现分布式锁 1.根据lockKey区进行setnx(set not exist,如果key值为空,则正常设置,返回1,否则不会进行设置并返回0)操作,如果设置成功,表示已经获得锁,否则并没 ...

  9. Zookeeper系列四:Zookeeper实现分布式锁、Zookeeper实现配置中心

    一.Zookeeper实现分布式锁 分布式锁主要用于在分布式环境中保证数据的一致性. 包括跨进程.跨机器.跨网络导致共享资源不一致的问题. 1. 分布式锁的实现思路 说明: 这种实现会有一个缺点,即当 ...

  10. 10分钟看懂!基于Zookeeper的分布式锁

    实现分布式锁目前有三种流行方案,分别为基于数据库.Redis.Zookeeper的方案,其中前两种方案网络上有很多资料可以参考,本文不做展开.我们来看下使用Zookeeper如何实现分布式锁. 什么是 ...

随机推荐

  1. python+selenium初学者常见问题处理

    要做web自动化,第一件事情就是搭建自动化测试环境,那就没法避免的要用到selenium了. 那在搭建环境和使用过程中经常会遇到以下几类问题: 1.引入selenium包失败: 出现这种错误,一般分为 ...

  2. Java学习笔记——继承、接口、多态

    浮点数的运算需要注意的问题: BigDecimal operand1 = new BigDecimal("1.0"); BigDecimal operand2 = new BigD ...

  3. oracle环境变量详解

    共享存储文件系统(NFS) 通常情况下,ORACLE_SID这个环境变量全称Oracle System Identifier,,用于在一台服务器上标识不同的实例,默认情况下,实例名就是ORACLE_S ...

  4. 洛谷 P4559: bzoj 5319: [JSOI2018]军训列队

    题目传送门:洛谷 P4559. 题意简述: 有 \(n\) 个学生,编号为 \(i\) 的学生有一个位置 \(a_i\). 有 \(m\) 个询问,每次询问编号在 \([l,r]\) 区间内的学生跑到 ...

  5. 理解mipi协议【转】

    转自:http://blog.csdn.net/wanglining1987/article/details/50202615 完成mipi信号通道分配后,需要生成与物理层对接的时序.同步信号: MI ...

  6. linux用户权限 -> 系统基本权限

    比如rwxr-xr-x linux中正是这9个权限位来控制文件属主(User).属组(Group).其他用户(Other)基础权限. 用户对资源来说, 有三种角色 User(u): 属主用户(文件所有 ...

  7. 用js面向对象思想封装插件

    js是基于原型的面向对象语言,如果你学过java,c#等正统面向对象语言,你会难以理解js的面向对象,他和普通的面向对象不太一样,今天,我们通过封装一个toast插件,来看看js面向对象是如何运行的. ...

  8. 洛谷P3366最小生成树

    传送门啦 #include <iostream> #include <cstdio> #include <cstring> #include <algorit ...

  9. vue-cli脚手架安装

    -1.安装淘宝镜像 $ alias cnpm="npm --registry=https://registry.npm.taobao.org \ --cache=$HOME/.npm/.ca ...

  10. 浅谈malloc/free和new/delete 的区别

    malloc和new的区别 malloc是库函数,需要包头文件才能成功运行编译:new是操作符(C++中的关键字),需要在C++的环境下使用. malloc既可以在C语言中使用也可以在C++中使用,n ...