热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Zookeeper实现分布式锁(一)While版

前面文章讲解了用Redis实现分布式锁的方式:分布式锁之Redis实现(acquire)分布式锁之Redis实现(最终版)这次我们来使用Zookeeper来实现分布式锁核心逻辑我们

前面文章讲解了用Redis实现分布式锁的方式:

分布式锁之Redis实现(acquire)
分布式锁之Redis实现(最终版)

这次我们来使用Zookeeper来实现分布式锁

核心逻辑

我们使用Zookeeper同名节点只能创建一次的特性,来实现独占锁,具体方法为:

try {
zk.create(lockNameSpace, "value".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
} catch (KeeperException e) {
//如果节点已经被其他创建,捕获异常
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}

所有客户端用create方法创建名称为lockNameSpace变量值的节点,如果创建成功则拿到锁,创建失败抛出NodeExists 异常:

org.apache.zookeeper.KeeperException$NodeExistsException: KeeperErrorCode = NodeExists for /mylock
at org.apache.zookeeper.KeeperException.create(KeeperException.java:119)
at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
at org.apache.zookeeper.ZooKeeper.create(ZooKeeper.java:783)
at zookeeper.zookeeper.tttt.main(tttt.java:49)

说明获取锁失败。

具体实现

根据create方法的特性,我们来实现一个最简版的分布式锁

1.获取锁

public boolean lock() throws InterruptedException {
String path = null;
watchNode(nodeString);
while (true) {
try {
path = zk.create(nodeString, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
} catch (KeeperException e) {
System.out.println(Thread.currentThread().getName() + "请求失败");
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
System.out.println("thread is notify");
}
}
if (path!=null&&!path.isEmpty()) {
System.out.println(Thread.currentThread().getName() + " getLock...");
return true;
}
}
}

代码逻辑:

path是create方法的返回值,用来判断是否创建成功。
while循环用来获取锁失败后不断重试
watchNode()方法用来监听nodeString的状态

watchNode:

private void watchNode(String nodeString) throws InterruptedException {
try {
zk.exists(nodeString, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("事件来了:" + watchedEvent.toString());
if (watchedEvent.getType() == Event.EventType.NodeDeleted) {
System.out.println("delete事件");
}
}
});
} catch (KeeperException e) {
e.printStackTrace();
}
}

2.释放锁

public void unlock() {
try {
zk.delete(nodeString, -1);
System.out.println(Thread.currentThread().getName() + "release Lock...");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}

代码很简单,删除nodeString节点,以便其它客户端来竞争锁。

3.main方法

public static void main(String args[]) throws InterruptedException {
Zookeeper_1_While test = new Zookeeper_1_While();
try {
Thread.sleep(100);
zk.create(lockNameSpace, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
} catch (KeeperException e) {
} catch (InterruptedException e) {
}
ExecutorService service = Executors.newFixedThreadPool(10);
for (int i = 0; i <4; i++) {
service.execute(() -> {
try {
test.lock();
Thread.sleep(1800);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.unlock();
});
}
service.shutdown();
}

创建四个客户端来竞争锁,竞争到以后执行1.8s的业务逻辑,然后释放锁。

4.输出结果

Receive event WatchedEvent state:SyncConnected type:None path:null
connection is ok
事件来了:WatchedEvent state:SyncConnected type:NodeCreated path:/mylock/lock1
事件来了:WatchedEvent state:SyncConnected type:NodeCreated path:/mylock/lock1
事件来了:WatchedEvent state:SyncConnected type:NodeCreated path:/mylock/lock1
事件来了:WatchedEvent state:SyncConnected type:NodeCreated path:/mylock/lock1
pool-1-thread-1 拿到 Lock...
pool-1-thread-2请求失败
pool-1-thread-3请求失败
pool-1-thread-4请求失败
pool-1-thread-2请求失败
pool-1-thread-4请求失败
pool-1-thread-3请求失败
pool-1-thread-2请求失败
pool-1-thread-4请求失败
pool-1-thread-3请求失败
pool-1-thread-2请求失败
pool-1-thread-4请求失败
pool-1-thread-3请求失败
pool-1-thread-1释放 Lock...
pool-1-thread-2 拿到 Lock...
pool-1-thread-4请求失败
pool-1-thread-3请求失败
pool-1-thread-4请求失败
pool-1-thread-3请求失败
pool-1-thread-4请求失败
pool-1-thread-3请求失败
pool-1-thread-4请求失败
pool-1-thread-3请求失败
pool-1-thread-2释放 Lock...
pool-1-thread-4 拿到 Lock...
pool-1-thread-3请求失败
pool-1-thread-3请求失败
pool-1-thread-3请求失败
pool-1-thread-3请求失败
pool-1-thread-4释放 Lock...
pool-1-thread-3 拿到 Lock...
pool-1-thread-3释放 Lock...

存在的问题

通过打印的输出结果我们可以看到,同时竞争锁的客户端越多,请求失败的输出越频繁,也就是while循环去请求create方法越频繁,这会导致两个问题

1.while循环浪费cpu
2.频繁进行create重试会对Zookeeper服务器造成压力

githup代码:https://github.com/granett/zookeeper/blob/master/src/main/java/zookeeper/zookeeper/Zookeeper_1_While.java
针对以上两个问题,我们后续在下一篇Zookeeper实现分布式锁(二)Watcher版进行解决和优化。


推荐阅读
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 标题: ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 本文讨论了在VMWARE5.1的虚拟服务器Windows Server 2008R2上安装oracle 10g客户端时出现的问题,并提供了解决方法。错误日志显示了异常访问违例,通过分析日志中的问题帧,找到了解决问题的线索。文章详细介绍了解决方法,帮助读者顺利安装oracle 10g客户端。 ... [详细]
  • ***byte(字节)根据长度转成kb(千字节)和mb(兆字节)**parambytes*return*publicstaticStringbytes2kb(longbytes){ ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 大数据Hadoop生态(20)MapReduce框架原理OutputFormat的开发笔记
    本文介绍了大数据Hadoop生态(20)MapReduce框架原理OutputFormat的开发笔记,包括outputFormat接口实现类、自定义outputFormat步骤和案例。案例中将包含nty的日志输出到nty.log文件,其他日志输出到other.log文件。同时提供了一些相关网址供参考。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • mac php错误日志配置方法及错误级别修改
    本文介绍了在mac环境下配置php错误日志的方法,包括修改php.ini文件和httpd.conf文件的操作步骤。同时还介绍了如何修改错误级别,以及相应的错误级别参考链接。 ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • 本文讨论了在shiro java配置中加入Shiro listener后启动失败的问题。作者引入了一系列jar包,并在web.xml中配置了相关内容,但启动后却无法正常运行。文章提供了具体引入的jar包和web.xml的配置内容,并指出可能的错误原因。该问题可能与jar包版本不兼容、web.xml配置错误等有关。 ... [详细]
author-avatar
王漻_957
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有