目前分布式锁,比较成熟、主流的方案有基于redis及基于zookeeper的二种方案。

  大体来讲,基于redis的分布式锁核心指令为SETNX,即如果目标key存在,写入缓存失败返回0,反之如果目标key不存在,写入缓存成功返回1,通过区分这二个不同的返回值,可以认为SETNX成功即为获得了锁。

  redis分布式锁,看上去很简单,但其实要考虑周全,并不容易,网上有一篇文章讨论得很详细:http://blog.csdn.net/ugg/article/details/41894947/,有兴趣的可以阅读一下。

  其主要问题在于某些异常情况下,锁的释放会有问题,比如SETNX成功,应用获得锁,这时出于某种原因,比如网络中断,或程序出异常退出,会导致锁无法及时释放,只能依赖于缓存的过期时间,但是过期时间这个值设置多大,也是一个纠结的问题,设置小了,应用处理逻辑很复杂的话,可能会导致锁提前释放,如果设置大了,又会导致锁不能及时释放,所以那篇文章中针对这些细节讨论了很多。

  而基于zk的分布式锁,在锁的释放问题上处理起来要容易一些,其大体思路是利用zk的“临时顺序”节点,需要获取锁时,在某个约定节点下注册一个临时顺序节点,然后将所有临时节点按小从到大排序,如果自己注册的临时节点正好是最小的,表示获得了锁。(zk能保证临时节点序号始终递增,所以如果后面有其它应用也注册了临时节点,序号肯定比获取锁的应用更大)

  当应用处理完成,或者处理过程中出现某种原因,导致与zk断开,超过时间阈值(可配置)后,zk server端会自动删除该临时节点,即:锁被释放。所有参与锁竞争的应用,只要监听父路径的子节点变化即可,有变化时(即:有应用断开或注册时),开始抢锁,抢完了大家都在一边等着,直到有新变化时,开始新一轮抢锁。

  关于zk的分布式锁,网上也有一篇文章写得不错,见http://blog.csdn.net/desilting/article/details/41280869

个人感觉:zk做分布式锁机制更完善,但zk抗并发的能力弱于redis,性能上略差,建议如果并发要求高,锁竞争激烈,可考虑用redis,如果抢锁的频度不高,用zk更适合。

最后送福利时间到:

  文中提到的基于zk分布式锁的那篇文章,逻辑上虽然没有问题,但是有些场景下,锁的数量限制可能要求不止1个,比如:某些应用,我希望同时启动2个实例来处理,但是出于HA的考虑,又担心这二个实例会挂掉,这时可以启动4个(或者更多),这些实例中,只允许2个抢到锁的实例可以进行业务处理,其它实例处于standby状态(即:备胎),如果这二个抢到锁的实例挂了(比如异常退出),那么standby的实例会得到锁,即:备胎转正,开始正常业务处理,从而保证了系统的HA。

对于这些场景,我封装了一个抽象类,大家可在此基础上自行修改:(主要看明白思路就行,代码细节并不重要)

  1. package cn.cnblogs.yjmyzz.zookeeper;
  2.  
  3. import org.I0Itec.zkclient.ZkClient;
  4. import org.apache.commons.collections4.CollectionUtils;
  5. import org.apache.commons.lang3.StringUtils;
  6. import org.slf4j.Logger;
  7. import org.slf4j.LoggerFactory;
  8.  
  9. import java.util.Collections;
  10. import java.util.Date;
  11. import java.util.List;
  12. import java.util.concurrent.Executors;
  13. import java.util.concurrent.ScheduledExecutorService;
  14. import java.util.concurrent.TimeUnit;
  15.  
  16. /**
  17. * Created by yangjunming on 5/27/16.
  18. * 基于Zookeeper的分布式锁
  19. */
  20. public abstract class AbstractLock {
  21.  
  22. private int lockNumber = 1; //允许获取的锁数量(默认为1,即最小节点=自身时,认为获得锁)
  23. private ZkClient zk = null;
  24. private String rootNode = "/lock"; //根节点名称
  25. private String selfNode;
  26. private final String className = this.getClass().getSimpleName(); //当前实例的className
  27. private String selfNodeName;//自身注册的临时节点名
  28. private boolean handling = false;
  29. protected final Logger logger = LoggerFactory.getLogger(this.getClass());
  30. private static final JsonUtil jsonUtil = new FastJsonUtil();
  31. private static final String SPLIT = "/";
  32. private String selfNodeFullName;
  33.  
  34. /**
  35. * 通过Zk获取分布式锁
  36. */
  37. protected void getLock(int lockNumber) {
  38. setLockNumber(lockNumber);
  39. initBean();
  40. initNode();
  41. subscribe();
  42. register();
  43. heartBeat();
  44. remainRunning();
  45. }
  46.  
  47. protected void getLock() {
  48. getLock(1);
  49. }
  50.  
  51. /**
  52. * 初始化结点
  53. */
  54. private void initNode() {
  55.  
  56. String error;
  57. if (!rootNode.startsWith(SPLIT)) {
  58. error = "rootNode必须以" + SPLIT + "开头";
  59. logger.error(error);
  60. throw new RuntimeException(error);
  61. }
  62.  
  63. if (rootNode.endsWith(SPLIT)) {
  64. error = "不能以" + SPLIT + "结尾";
  65. logger.error(error);
  66. throw new RuntimeException(error);
  67. }
  68.  
  69. int start = 1;
  70. int index = rootNode.indexOf(SPLIT, start);
  71. String path;
  72. while (index != -1) {
  73. path = rootNode.substring(0, index);
  74. if (!zk.exists(path)) {
  75. zk.createPersistent(path);
  76. }
  77. start = index + 1;
  78. if (start >= rootNode.length()) {
  79. break;
  80. }
  81. index = rootNode.indexOf(SPLIT, start);
  82. }
  83.  
  84. if (start < rootNode.length()) {
  85. if (!zk.exists(rootNode)) {
  86. zk.createPersistent(rootNode);
  87. }
  88. }
  89.  
  90. selfNode = rootNode + SPLIT + className;
  91.  
  92. if (!zk.exists(selfNode)) {
  93. zk.createPersistent(selfNode);
  94. }
  95. }
  96.  
  97. /**
  98. * 向zk注册自身节点
  99. */
  100. private void register() {
  101. selfNodeName = zk.createEphemeralSequential(selfNode + SPLIT, StringUtils.EMPTY);
  102. if (!StringUtils.isEmpty(selfNodeName)) {
  103. selfNodeFullName = selfNodeName;
  104. logger.info("自身节点:" + selfNodeName + ",注册成功!");
  105. selfNodeName = selfNodeName.substring(selfNode.length() + 1);
  106. }
  107. checkMin();
  108. }
  109.  
  110. /**
  111. * 订阅zk的节点变化
  112. */
  113. private void subscribe() {
  114. zk.subscribeChildChanges(selfNode, (parentPath, currentChilds) -> {
  115. checkMin();
  116. });
  117. }
  118.  
  119. /**
  120. * 检测是否获得锁
  121. */
  122. private void checkMin() {
  123. List<String> list = zk.getChildren(selfNode);
  124. if (CollectionUtils.isEmpty(list)) {
  125. logger.error(selfNode + " 无任何子节点!");
  126. lockFail();
  127. handling = false;
  128. return;
  129. }
  130. //按序号从小到大排
  131. Collections.sort(list);
  132.  
  133. //如果自身ID在前N个锁中,则认为获取成功
  134. int max = Math.min(getLockNumber(), list.size());
  135. for (int i = 0; i < max; i++) {
  136. if (list.get(i).equals(selfNodeName)) {
  137. if (!handling) {
  138. lockSuccess();
  139. handling = true;
  140. logger.info("获得锁成功!");
  141. }
  142. return;
  143. }
  144. }
  145.  
  146. int selfIndex = list.indexOf(selfNodeName);
  147. if (selfIndex > 0) {
  148. logger.info("前面还有节点" + list.get(selfIndex - 1) + ",获取锁失败!");
  149. } else {
  150. logger.info("获取锁失败!");
  151. }
  152. lockFail();
  153.  
  154. handling = false;
  155. }
  156.  
  157. /**
  158. * 获得锁成功的处理回调
  159. */
  160. protected abstract void lockSuccess();
  161.  
  162. /**
  163. * 获得锁失败的处理回调
  164. */
  165. protected abstract void lockFail();
  166.  
  167. /**
  168. * 初始化相关的Bean对象
  169. */
  170. protected abstract void initBean();
  171.  
  172. protected void setZkClient(ZkClient zk) {
  173. this.zk = zk;
  174. }
  175.  
  176. protected int getLockNumber() {
  177. return lockNumber;
  178. }
  179.  
  180. protected void setLockNumber(int lockNumber) {
  181. this.lockNumber = lockNumber;
  182. }
  183.  
  184. protected void setRootNode(String value) {
  185. this.rootNode = value;
  186. }
  187.  
  188. /**
  189. * 防程序退出
  190. */
  191. private void remainRunning() {
  192. byte[] lock = new byte[0];
  193. synchronized (lock) {
  194. try {
  195. lock.wait();
  196. } catch (InterruptedException e) {
  197. Thread.currentThread().interrupt();
  198. logger.error("remainRunning出错:", e);
  199. }
  200. }
  201. }
  202.  
  203. /**
  204. * 定时向zk发送心跳
  205. */
  206. private void heartBeat() {
  207. ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
  208. service.scheduleAtFixedRate(() -> {
  209. HeartBeat heartBeat = new HeartBeat();
  210. heartBeat.setHostIp(NetworkUtil.getHostAddress());
  211. heartBeat.setHostName(NetworkUtil.getHostName());
  212. heartBeat.setLastTime(new Date());
  213. heartBeat.setPid(RuntimeUtil.getPid());
  214. zk.writeData(selfNodeFullName, jsonUtil.toJson(heartBeat));
  215. }, 0, 15, TimeUnit.SECONDS);
  216. }
  217. }

这个类中,提供了三个抽象方法:

  1. /**
  2. * 获得锁成功的处理回调
  3. */
  4. protected abstract void lockSuccess();
  5.  
  6. /**
  7. * 获得锁失败的处理回调
  8. */
  9. protected abstract void lockFail();
  10.  
  11. /**
  12. * 初始化相关的Bean对象
  13. */
  14. protected abstract void initBean();

用于处理抢锁成功、抢锁失败、及开抢前的一些对象初始化处理,子类继承后,只要实现这3个具体的方法即可,同时该抽象类默认还提供了心跳机制,用于定时向zk汇报自身的健康状态。

ZooKeeper 笔记(6) 分布式锁的更多相关文章

  1. zookeeper 笔记--curator分布式锁

    使用ZK实现分布式独占锁, 原理就是利用ZK同级节点的唯一性. Curator框架下的一些分布式锁工具InterProcessMutex:分布式可重入排它锁 InterProcessSemaphore ...

  2. 如何用Zookeeper来实现分布式锁?

    什么是Zookeeper临时顺序节点? 例如 : / 动物 植物 猫 仓鼠 荷花 松树 Zookeeper的数据存储结构就像一棵树,这棵树由节点组成,这种节点叫做Zonde.# Znode分为四种类型 ...

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

    基于zookeeper实现的分布式锁 2011-01-27 • 技术 • 7 条评论 • jiacheo •14,941 阅读 A distributed lock base on zookeeper ...

  4. java使用zookeeper实现的分布式锁示例

    java使用zookeeper实现的分布式锁示例 作者: 字体:[增加 减小] 类型:转载 时间:2014-05-07我要评论 这篇文章主要介绍了java使用zookeeper实现的分布式锁示例,需要 ...

  5. 基于Zookeeper实现多进程分布式锁

    一.zookeeper简介及基本操作 Zookeeper 并不是用来专门存储数据的,它的作用主要是用来维护和监控你存储的数据的状态变化.当对目录节点监控状态打开时,一旦目录节点的状态发生变化,Watc ...

  6. 利用ZooKeeper简单实现分布式锁

    1.分布式锁的由来: 在程序开发过程中不得不考虑的就是并发问题.在java中对于同一个jvm而言,jdk已经提供了lock和同步等.但是在分布式情况下,往往存在多个进程对一些资源产生竞争关系,而这些进 ...

  7. 基于zookeeper简单实现分布式锁

    https://blog.csdn.net/desilting/article/details/41280869 这里利用zookeeper的EPHEMERAL_SEQUENTIAL类型节点及watc ...

  8. 基于zookeeper实现高性能分布式锁

    实现原理:利用zookeeper的持久性节点和Watcher机制 具体步骤: 1.创建持久性节点 zkLock 2.在此父节点下创建子节点列表,name按顺序定义 3.Java程序获取该节点下的所有顺 ...

  9. zookeeper实现的分布式锁

    在分布式系统中,多个jvm对共享资源进行操作时候,要加上锁,这就是分布式锁 利用zookeeper的临时节点的特性,可以实现分布式锁 public class ZookeeperDistrbuteLo ...

随机推荐

  1. jQuery源码学习感想

    还记得去年(2015)九月份的时候,作为一个大四的学生去参加美团霸面,结果被美团技术总监教育了一番,那次问了我很多jQuery源码的知识点,以前虽然喜欢研究框架,但水平还不足够来研究jQuery源码, ...

  2. Vertica增加一个数据存储的目录

    Vertica增加一个数据存储的目录 操作语法为: ADD_LOCATION ( 'path' , [ 'node' , 'usage', 'location_label' ] ) 各节点添加目录,并 ...

  3. java笔记--理解java类加载器以及ClassLoader类

    类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制 ...

  4. 《转载》PAT 习题

    博客出处:http://blog.csdn.net/zhoufenqin/article/details/50497791 题目出处:https://www.patest.cn/contests/pa ...

  5. Windows Programming ---- Beginning Visual C#

    span.kw { color: #007020; font-weight: bold; } code > span.dt { color: #902000; } code > span. ...

  6. PHP环境配置

    PHP环境配置 1.Apache的安装 第一步: 1.    双击httpd-2.2.17-win32-x86-no_ssl.msi.出现 Windows 标准的软件安装欢迎界面,直接点“Next”继 ...

  7. 如何实现一个php框架系列文章【开篇】

    1.本系列文章的目的 实现一个小而美的产品级别php框架 自己动手实现一个新框架仅用于学习交流,不打算替代市面上现有的其他主流框架. 2. 我要一个怎样的PHP框架 简单实用,安全优雅,博采众长 安装 ...

  8. Hibernate-模板模式

    在我的博客<Hibernate总结(一)>在对数据库的增删改查前后重复的使用了得到Session与关闭Session等操作,因此我想到了模板设计模式. 模板设计模式概述: 定义一个操作中的 ...

  9. 常见容易遗漏的html标签

    <link href="favicon.ico" mce_href="/favicon.ico" rel="bookmark" typ ...

  10. CSS3_01之选择器、Hack

    1.兄弟选择器:①相邻兄弟选择器:元素的后一个兄弟元素,选择器1+选择器2:②通用兄弟选择器:元素后的所有兄弟元素,选择器1~选择器2: 2.属性选择器:attr表示属性名称,elem表示元素名:①[ ...