作者:Grey

原文地址: ZooKeeper学习笔记四:使用ZooKeeper实现一个简单的分布式锁

前置知识

完成ZooKeeper集群搭建以及熟悉ZooKeeperAPI基本使用

需求

当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。

在单机情况下,可以使用JUC包里面的工具来进行互斥控制。

但是在分布式系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机并发控制锁策略失效,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁的由来。

当多个进程不在同一个系统中,就需要用分布式锁控制多个进程对资源的访问。

我们可以用ZooKeeper来模拟实现一个简单的分布式锁

环境准备

一个zk集权,ip和端口分别为:

  • 192.168.205.145:2181
  • 192.168.205.146:2181
  • 192.168.205.147:2181
  • 192.168.205.148:2181

定义主方法

App.java

  1. public class App {
  2. public static void main(String[] args) {
  3. for (int i = 0; i < 10; i++) {
  4. new Thread(() -> {
  5. ZkLock lock = new ZkLock();
  6. lock.lock(); // 开启锁
  7. System.out.println(Thread.currentThread().getName() + " doing work");
  8. lock.release(); // 释放锁
  9. }).start();
  10. }
  11. while (true) {
  12. }
  13. }
  14. }

如上,我们设计了一个ZkLock,其中lock方法是锁定资源,release方法是释放资源,我们并发了10个线程并发访问来模拟。

  1. public class ZkLock implements AsyncCallback.StringCallback, Watcher, AsyncCallback.StatCallback, AsyncCallback.Children2Callback {
  2. private CountDownLatch latch;
  3. private ZooKeeper zk;
  4. private String identify;
  5. private String lockPath;
  6. private String pathName;
  7. public ZkLock() {
  8. identify = Thread.currentThread().getName();
  9. lockPath = "/lock";
  10. latch = new CountDownLatch(1);
  11. zk = ZookeeperConfig.create(ADDRESS + "/testLock");
  12. }
  13. public void lock() {
  14. try {
  15. zk.create(lockPath, currentThread().getName().getBytes(UTF_8), OPEN_ACL_UNSAFE, EPHEMERAL_SEQUENTIAL, this, currentThread().getName());
  16. latch.await();
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. public void release() {
  22. try {
  23. zk.delete(pathName, -1);
  24. System.out.println(identify + " over work....");
  25. } catch (InterruptedException | KeeperException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. @Override
  30. public void processResult(int rc, String path, Object ctx, String name) {
  31. if (null != name) {
  32. // 创建成功
  33. System.out.println(identify + " created " + name);
  34. pathName = name;
  35. zk.getChildren("/", false, this, "dasdfas");
  36. }
  37. }
  38. @Override
  39. public void processResult(int rc, String path, Object ctx, List<String> children, Stat stat) {
  40. sort(children);
  41. int i = children.indexOf(pathName.substring(1));
  42. if (i == 0) {
  43. // 是第一个,获得锁,可以执行
  44. System.out.println(identify + " first...");
  45. try {
  46. zk.setData("/", identify.getBytes(UTF_8), -1);
  47. } catch (KeeperException | InterruptedException e) {
  48. e.printStackTrace();
  49. }
  50. latch.countDown();
  51. } else {
  52. zk.exists("/" + children.get(i - 1), this, this, "ddsdf");
  53. }
  54. }
  55. @Override
  56. public void process(WatchedEvent event) {
  57. switch (event.getType()) {
  58. case None:
  59. break;
  60. case NodeCreated:
  61. break;
  62. case NodeDeleted:
  63. zk.getChildren("/", false, this, "sdf");
  64. break;
  65. case NodeDataChanged:
  66. break;
  67. case NodeChildrenChanged:
  68. break;
  69. }
  70. }
  71. @Override
  72. public void processResult(int rc, String path, Object ctx, Stat stat) {
  73. }
  74. }

关于上述代码的说明,我们规定创建的zk目录为/testLock,所以我们可以通过zk客户端在集群中先把/testLock目录建好,后续线程争抢的时候,我们只需要创建序列化的临时节点(以/lock开头),因为是序列化的,所以我们可以设置让第一个创建好节点的线程抢到锁,其他的线程排队等待。

所以lock方法实现如下:

  1. zk.create(lockPath, currentThread().getName().getBytes(UTF_8), OPEN_ACL_UNSAFE, EPHEMERAL_SEQUENTIAL, this, currentThread().getName());

lock方法在执行的时候,会有一个回调,即:当节点创建成功后,会判断/testLock节点中有没有已经创建好的且在当前节点之前的节点,有的话,则注册一个一个对于/testLock目录的监听:

  1. @Override
  2. public void processResult(int rc, String path, Object ctx, String name) {
  3. if (null != name) {
  4. // 创建成功
  5. System.out.println(identify + " created " + name);
  6. pathName = name;
  7. zk.getChildren("/", false, this, "dasdfas");
  8. }
  9. }

一旦发现/testLock目录下已经有节点了,那么我们拿到/testLock下的所有节点,并排序,取最小的那个节点执行即可:

  1. @Override
  2. public void processResult(int rc, String path, Object ctx, List<String> children, Stat stat) {
  3. sort(children);
  4. int i = children.indexOf(pathName.substring(1));
  5. if (i == 0) {
  6. // 是第一个,获得锁,可以执行
  7. System.out.println(identify + " first...");
  8. try {
  9. zk.setData("/", identify.getBytes(UTF_8), -1);
  10. } catch (KeeperException | InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. latch.countDown();
  14. } else {
  15. zk.exists("/" + children.get(i - 1), this, this, "ddsdf");
  16. }
  17. }

release方法很简单,只需要把当前执行完毕的节点删除即可:

  1. public void release() {
  2. try {
  3. zk.delete(pathName, -1);
  4. System.out.println(identify + " over work....");
  5. } catch (InterruptedException | KeeperException e) {
  6. e.printStackTrace();
  7. }
  8. }

运行效果

确保zk中有/testLock这个节点,如果没有,请先创建一个:

Run App.java

可以看到控制台输出:

  1. Thread-5 created /lock0000000000
  2. Thread-4 created /lock0000000001
  3. Thread-1 created /lock0000000002
  4. Thread-9 created /lock0000000003
  5. Thread-6 created /lock0000000004
  6. Thread-2 created /lock0000000005
  7. Thread-3 created /lock0000000006
  8. Thread-0 created /lock0000000007
  9. Thread-8 created /lock0000000008
  10. Thread-7 created /lock0000000009
  11. Thread-5 first...
  12. Thread-5 doing work
  13. Thread-5 over work....
  14. Thread-4 first...
  15. Thread-4 doing work
  16. Thread-4 over work....
  17. Thread-1 first...
  18. Thread-1 doing work
  19. Thread-1 over work....
  20. Thread-9 first...
  21. Thread-9 doing work
  22. Thread-9 over work....
  23. Thread-6 first...
  24. Thread-6 doing work
  25. Thread-6 over work....
  26. Thread-2 first...
  27. Thread-2 doing work
  28. Thread-2 over work....
  29. Thread-3 first...
  30. Thread-3 doing work
  31. Thread-3 over work....
  32. Thread-0 first...
  33. Thread-0 doing work
  34. Thread-0 over work....
  35. Thread-8 first...
  36. Thread-8 doing work
  37. Thread-8 over work....
  38. Thread-7 first...
  39. Thread-7 doing work
  40. Thread-7 over work....

ZooKeeper学习笔记四:使用ZooKeeper实现一个简单的分布式锁的更多相关文章

  1. ensorflow学习笔记四:mnist实例--用简单的神经网络来训练和测试

    http://www.cnblogs.com/denny402/p/5852983.html ensorflow学习笔记四:mnist实例--用简单的神经网络来训练和测试   刚开始学习tf时,我们从 ...

  2. QML学习笔记(五)— 做一个简单的待做事项列表

    做一个简单的QML待做事项列表,能够动态添加和删除和编辑数据 GitHub:八至 作者:狐狸家的鱼 本文链接:QML学习笔记(五)— 做一个待做事项列表 主要用到QML:ListView 效果 全部代 ...

  3. 【WPF】学习笔记(一)——做一个简单的电子签名板

    参加实习(WPF)已经有两个多周的时间了,踩了一些坑,也算积累了一些小东西,准备慢慢拿出来分享一下.(●'◡'●) 这次呢就讲讲一个简单的电子签名板的实现. 先上张图(PS:字写得比较丑,不要太在意哈 ...

  4. Directx11学习笔记【十三】 实现一个简单地形

    本文由zhangbaochong原创,转载请注明出处http://www.cnblogs.com/zhangbaochong/p/5510294.html 上一个教程我们实现了渲染一个会旋转的立方体, ...

  5. Directx11学习笔记【十一】 画一个简单的三角形--effect框架的使用

    这里不再介绍effect框架的具体使用,有关effect框架使用可参考http://www.cnblogs.com/zhangbaochong/p/5475961.html 实现的功能依然是画一个简单 ...

  6. 【转】Redis学习笔记(五)如何用Redis实现分布式锁(2)—— 集群版

    原文地址:http://bridgeforyou.cn/2018/09/02/Redis-Dsitributed-Lock-2/ 单机版实现的局限性 在上一篇文章中,我们讨论了Redis分布式锁的实现 ...

  7. Redis学习笔记(三)使用Lua脚本实现分布式锁

    Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行. 使用Lua脚本的好处如下: 1.减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放 ...

  8. 使用redis设计一个简单的分布式锁

    最近看了有关redis的一些东西,了解了redis的一下命令,就记录一下: redis中的setnx命令: 关于redis的操作命令,我们一般会使用set,get等一系列操作,数据结构也有很多,这里我 ...

  9. Directx11学习笔记【十】 画一个简单的三角形

    本篇笔记要实现的是在屏幕上渲染出一个三角形,重点要学习的是渲染一个几何体的流程方式. 为了渲染几何图形,需要一个顶点缓存和一个描述顶点布局的输入层,还有着色器(主要是顶点着色器和像素着色器),下面来看 ...

随机推荐

  1. Selenium3自动化测试【17】元素定位之Link定位

    Link定位 find_element_by_link_text方法是通过文本链接来定位元素. 以Bing首页中顶部的[学术]链接为例,如图所示. 查看对应的html代码.从html中我们能看出这是一 ...

  2. 030- 控制语句if…else…

    语法: 第一种结构: if(boolean表达式){ java语句; } 解释:如果if后面的boolean表达式是true就执行大括号里面的java语句 如果是false就不执行大括号中的java语 ...

  3. vim 中文乱码解决

    问题如下: 在vim中编辑一个中文文本时 出现中文乱码情况 问题解决: 修改vimrc的脚本配置 编辑~/.vimrc文件,加上如下几行即可: set fileencodings=utf-8,ucs- ...

  4. hdu4971 流-最大权闭包

    题意:       给了一些任务,然后给了一些完成某些任务的限制,然后又给了限制之间的拓扑关系,最后问你最大收益. 思路:       很直白,就是流的一个应用,最大权闭包,没涉及到什么想法的地方,建 ...

  5. Python中python-nmap模块的使用

    目录 python-nmap的安装 python-nmap模块的使用 portScanner()类 环境:  python 2.7.13 Windows和Linux默认都是不安装python-nmap ...

  6. Java中的反射机制Reflection

    目录 什么是反射? 获取.class字节码文件对象 获取该.class字节码文件对象的详细信息 通过反射机制执行函数 反射链 反射机制是java的一个非常重要的机制,一些著名的应用框架都使用了此机制, ...

  7. Method Overlonding

    The method overloading is using one single method name with different parameters to created differen ...

  8. layui中的视频上传(PHP )

    1.html中: <div class="layui-form-item"> <label class="layui-form-label"& ...

  9. Java 中 RMI 的使用

    RMI 介绍 RMI (Remote Method Invocation) 模型是一种分布式对象应用,使用 RMI 技术可以使一个 JVM 中的对象,调用另一个 JVM 中的对象方法并获取调用结果.这 ...

  10. [Django框架 - 注意事项,安装,项目搭建,小白必会三板斧]

    [Django框架 - 注意事项,安装,项目搭建,小白必会三板斧] 想要正常运行django项目所需要知道的注意事项 1. 计算机名称不能有中文,不然bug在哪儿你都不知道! 2. 项目名和py文件名 ...