一.前言

在之前的文章中介绍过分布式锁的特点和利用Redis实现简单的分布式锁。但是分布式锁的实现还有很多其他方式,但是万变不离其宗,始终遵循一个特点:同一时刻只能有一个操作获取。这篇文章主要介绍如何基于zookeeper实现分布式锁。

  • zookeeper能够作为分布式锁实现的基础
  • 算法流程
  • 实现

关于分布式锁的相关特性,这里不再赘述,请参考分布式锁

### 二.zookeeper能够作为分布式锁实现的基础

这里回顾下分布式锁的特点:

  • 每次只能一个占用锁;
  • 可以重复进入锁;
  • 只有占用者才可以解锁;
  • 获取锁和释放锁都需要原子
  • 不能产生死锁
  • 尽量满足性能

zookeeper中有一种临时顺序节点,它具有以下特征:

  • 时效性,当会话结束,节点将自动被删除
  • 顺序性,当多个应用向其注册顺序节点时,每个顺序号将只能被一个应用获取

利用以上的特点可以满足分布式锁实现的基本要求:

  1. 因为顺序性,可以让最小顺序号的应用获取到锁,从而满足分布式锁的每次只能一个占用锁,因为只有它一个获取到,所以可以实现重复进入,只要设置标识即可。锁的释放,即删除应用在zookeeper上注册的节点,因为每个节点只被自己注册拥有,所以只有自己才能删除,这样就满足只有占用者才可以解锁

  2. zookeeper的序号分配是原子的,分配后即不会再改变,让最小序号者获取锁,所以获取锁是原子的

  3. 因为注册的是临时节点,在会话期间内有效,所以不会产生死锁

  4. zookeeper注册节点的性能能满足几千,而且支持集群,能够满足大部分情况下的性能

三.算法流程

1.获取锁

需要获取分布式锁的应用都向zookeeper的/lock/{resouce}目录下注册sequence-前缀的节点,序号最小者获取到操作资源的权限:

Note:

这里的resource需要依据竞争的具体资源确定,如竞争账户则可以使用账户号作为resource。

从图中可以看出,clientA的顺序号最小,由它获取到锁,操作资源。

算法步骤

  1. client判断/lock目录是否存在,如果不存在则向其注册/lock的持久节点
  2. client判断/lock目录下是否存在竞争的资源resouce目录,如果不存在则向其注册/lock/resource的持久节点
  3. client向/lock/resource目录下注册/lock/resource/sequence-前缀的临时顺序节点,并得到顺序号
  4. client获取/lock/resource目录下的所有临时顺序子节点
  5. client判断临时子节点序号中是否存在比自身的序号小的节点。如果不存在,则获取到锁;如果存在,则对象该临时节点做watch监控
  6. 如果收到监控的临时节点被删除的通知,则再重复4、5步骤,直到获取到锁

流程图

2.释放锁

因为最小的节点只被获取到锁的client持有,所以该锁不可能被其他client释放。同时释放锁只需要将临时顺序节点删除,也是原子性操作。

### 三.实现

  1. /**
  2. * 基于Zookeeper实现分布式锁
  3. *
  4. * @author huaijin
  5. */
  6. public class DistributedLockBaseZookeeper implements DistributedLock {
  7. private static final Logger log = LoggerFactory.getLogger(DistributedLockBaseZookeeper.class);
  8. /**
  9. * 利用空串作为各个节点存储的数据
  10. */
  11. private static final String EMPTY_DATA = "";
  12. /**
  13. * 分布式锁的根目录
  14. */
  15. private static final String LOCK_ROOT = "/lock";
  16. /**
  17. * zookeeper目录分隔符
  18. */
  19. private static final String PATH_SEPARATOR = "/";
  20. /**
  21. * 临时顺序节点前缀
  22. */
  23. private static final String LOCK_NODE_PREFIX = "sequence-";
  24. /**
  25. * 利用Lock和Condition实现等待通知
  26. */
  27. private Lock waitNotifierLock = new ReentrantLock();
  28. private Condition waitNotifier = waitNotifierLock.newCondition();
  29. /**
  30. * 操作zookeeper的client
  31. */
  32. private ZkClient zkClient;
  33. /**
  34. * 分布式资源的路径
  35. */
  36. private String resourcePath;
  37. /**
  38. * 锁节点完整前缀
  39. */
  40. private String lockNodePrefix;
  41. /**
  42. * 当前注册的临时顺序节点路径
  43. */
  44. private String currentLockNodePath;
  45. public DistributedLockBaseZookeeper(String resource, ZkClient zkClient) {
  46. Objects.requireNonNull(zkClient, "zkClient must not be null!");
  47. if (resource == null || resource.isEmpty()) {
  48. throw new IllegalArgumentException("resource must not be null!");
  49. }
  50. this.zkClient = zkClient;
  51. this.resourcePath = LOCK_ROOT + PATH_SEPARATOR + resource;
  52. this.lockNodePrefix = resourcePath + PATH_SEPARATOR + LOCK_NODE_PREFIX;
  53. // 创建分布式锁根目录
  54. if (!this.zkClient.exists(LOCK_ROOT)) {
  55. try {
  56. this.zkClient.create(LOCK_ROOT, EMPTY_DATA, CreateMode.PERSISTENT);
  57. } catch (ZkNodeExistsException e) {
  58. // ignore, logging
  59. log.warn("The root path for lock already exists.");
  60. }
  61. }
  62. // 创建资源目录
  63. if (!this.zkClient.exists(resourcePath)) {
  64. try {
  65. this.zkClient.create(resourcePath, EMPTY_DATA, CreateMode.PERSISTENT);
  66. } catch (ZkNodeExistsException e) {
  67. // ignore, logging
  68. log.warn("The resource path for [" + resourcePath + "] already exists.");
  69. }
  70. }
  71. }
  72. @Override
  73. public void lock() throws DistributedLockException {
  74. if (!acquireLock()) {
  75. // 如果获取锁不成功,则等待
  76. waitNotifierLock.lock();
  77. try {
  78. waitNotifier.await();
  79. } catch (Exception e) {
  80. throw new DistributedLockException("Interrupt when waiting notification.");
  81. } finally {
  82. waitNotifierLock.unlock();
  83. }
  84. }
  85. }
  86. @Override
  87. public void unlock() {
  88. // 删除自身节点,释放锁
  89. zkClient.delete(currentLockNodePath);
  90. }
  91. private boolean acquireLock() throws DistributedLockException {
  92. // 如果当前未注册临时顺序节点,则注册
  93. if (this.currentLockNodePath == null) {
  94. this.currentLockNodePath = zkClient.create(lockNodePrefix, EMPTY_DATA, CreateMode.EPHEMERAL_SEQUENTIAL);
  95. }
  96. // 获取顺序号
  97. long lockNodeSeq = fetchSeqFromNodePath(currentLockNodePath);
  98. // 获取所有子节点
  99. List<String> childNodePaths = zkClient.getChildren(resourcePath);
  100. if (childNodePaths == null || childNodePaths.isEmpty()) {
  101. throw new DistributedLockException("Not exists child nodes.");
  102. }
  103. // 从所有子节点中获取最小子节点的顺序号
  104. long minSeq = 1000000L;
  105. int minIndex = -1;
  106. for (int i = 0; i < childNodePaths.size(); i++) {
  107. long nodeSeq = fetchSeqFromNodePath(resourcePath + childNodePaths.get(i));
  108. if (nodeSeq < minSeq) {
  109. minSeq = nodeSeq;
  110. minIndex = i;
  111. }
  112. }
  113. // 比较自身顺序号与最小序号
  114. if (lockNodeSeq > minSeq) {
  115. // 如果存在更小序号,则监控最小序号的子节点
  116. String minLockNodePath = childNodePaths.get(minIndex);
  117. zkClient.subscribeDataChanges(resourcePath + PATH_SEPARATOR + minLockNodePath,
  118. new ListenerForLockRelease());
  119. return false;
  120. }
  121. // 成功获取锁,返回
  122. return true;
  123. }
  124. private long fetchSeqFromNodePath(String nodePath) {
  125. String seq = nodePath.substring(lockNodePrefix.length());
  126. return Long.valueOf(seq);
  127. }
  128. private class ListenerForLockRelease implements IZkDataListener {
  129. @Override
  130. public void handleDataChange(String dataPath, Object data) throws Exception {
  131. }
  132. @Override
  133. public void handleDataDeleted(String dataPath) throws Exception {
  134. // 如果成功获取锁,则通知,让主线程返回
  135. if (acquireLock()) {
  136. waitNotifierLock.lock();
  137. try {
  138. waitNotifier.signal();
  139. } finally {
  140. waitNotifierLock.unlock();
  141. }
  142. }
  143. }
  144. }
  145. }

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

  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. 为什么有的插件安装需要用Vue.use()方法

    问题 相信很多人在用Vue使用别人的组件时,会用到 Vue.use() .例如:Vue.use(VueRouter).Vue.use(MintUI).但是用 axios时,就不需要用 Vue.use( ...

  2. Windows下使用virtualenv创建虚拟环境

    操作系统 : windowns10_x64Python版本:3.6.8virtualenv版本:16.7.7virtualenvwrapper版本:1.2.5 方式一:直接使用virtualenv 1 ...

  3. c++实现通讯录管理系统(控制台版)

    c++实现通讯录管理系统(控制台版) 此项目适合c++初学者,针对c++基础知识,涉及到变量.结构体定义使用.数组定义使用.指针定义使用等. 运行之后的结果如下: 代码: #include <i ...

  4. Java描述设计模式(07):适配器模式

    本文源码:GitHub·点这里 || GitEE·点这里 一.适配器模式简介 1.基础概念 适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在 ...

  5. Add a Class from the Business Class Library从业务类库添加类(EF)

    In this lesson, you will learn how to use business classes from the Business Class Library as is. Fo ...

  6. 下载EPM包详细运行日志

    事务码:UJFS,选择包运行的环境名称. 从root根目录下进入对应环境->模型目录,找到privatepublications 文件夹对应的账号文件夹(运行包的账号名称) 进入tempfile ...

  7. 三大免费开源的php语言cms系统 用好它们让你一天建好一个网站

    php语言只所以在web开发领域占据半壁江山,是因为它有太多的生态,成熟的框架体系,广泛的开源cms系统.建设网站的时候,都想提升开发效率,效率就是成本,如果你用原生php语言开发一个项目,既要设计数 ...

  8. 剑指offer 21:包含min函数的栈

    题目描述 定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1)). 解题思路 要求在O(1)时间内完成,由于栈后进先出的原则,不能出现破坏栈结构的事情.因 ...

  9. ABP入门教程14 - 更新多语言

    点这里进入ABP入门教程目录 设置语种 新增语种 数据库操作 打开多语言表AbpLanguages,添加一条记录. 程序操作 在基础设施层(即JD.CRS.EntityFrameworkCore)的\ ...

  10. 示例:Oracle表锁、行锁模拟和处理

    for update模拟锁表 --session 1 SQL> select * from tt for update; --session 2 SQL> update tt set id ...