ZooKeeper 实现分布式锁的简单示例

2023-10-27

一. 分布式锁概述

  1. 在分布式环境中,服务器集群,多个jvm运行,如果使用Lock或synchronized多个jvm是无法保证线程安全的
  2. 什么是分布式锁: 用来控制同一个任务的是执行,或设置任务顺序执行,保证同一时间内只有一个jvm执行某一个任务
  3. 分布式锁的实现方案: 基于数据库(效率较低),基于redis,基于ZooKeeper,SpringCloud全局锁
  4. redis 实现分布式锁可能产生的问题: 在redis主从架构中,通过setnx()向redis中存储锁,主节点接收数据,然后将数据同步到从节点,假设在同步未执行完毕时,主节点宕机,从节点升级为主节点,造成新的中节点上没有锁的问题
  5. 如何选择分布式锁: 根据实际的业务场景,假设当前项目并发要求较高可以使用redis,如果并发要求不高可以使用zookeeper

二. ZooKeeper 实现分布式锁

ZooKeeper 实现分布式锁原理

  1. ZooKeeper 可以充当一个文件系统,可以将数据以节点形式存入ZooKeeper中,并提供了增删改查,监听,通知等方法
  2. 当向ZooKeeper中发送请求时,集群环境下的ZooKeeper接收到请求首先会将请求转发给Leader,Leader在将请求广播给所有的Follwer,多Follwer同时处理请求,通过ZooKeeper对这个项目构架就可以找到一个唯一点
  3. ZooKeeper中存储数据的节点分为持久节点与临时节点,利用临时节点的特性,当连接断开,存储的临时节点数据消失
  4. ZooKeeper中不能创建两个相同的节点的特性

实现分析

假设在分布式集群环境中需要对某一段代码加锁,同一时间内只允许一个jvm的一个线程执行

  1. 在执行该代码以前先向ZooKeeper中创建一个临时节点例如"/Lock",该节点就可以看为锁
  2. 利用ZooKeeper中不允许创建相同节点的特性,如果创建失败,说明已有线程持有了锁
  3. 当获取锁失败时对该代表锁的节点设置监听,当前线程进行阻塞等待
  4. 当需要加锁执行的代码执行完毕后,关闭Zookeeper连接,代表锁的临时节点消失,通过ZooKeeper监听,回调方法执行唤醒其它获取锁失败阻塞等待的线程

代码示例

  1. 此处使用ZkClient 操作Zookeeper 项目中除了引入ZooKeeper的依赖以外还需要需要引入ZkClient 依赖, ZkClient 方法使用简介
	<!--zookeeper依赖-->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.4.13</version>
        </dependency>
        <!--管理zookeeper需要用到的-->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
            <version>4.0.1</version>
        </dependency>
        <!--zkclient-->
        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.10</version>
        </dependency>
  1. 注意使用 ZkClient 创建临时节点以后,需要通过ZkClient 调用close() 方法后,该临时节点才会消失
  2. 此处使用 CountDownLatch 信号量方式,来设置线程阻塞等待,与线程唤醒等
  3. 重点关注获取锁失败时的阻塞方法
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;

public class ZooKeeperLockTest {
    //运行测试
    public static void main(String[] args) throws InterruptedException {
        ZooKeeperLockTest zooKeeperLock = new ZooKeeperLockTest();

        //使用分布式锁实现任务方法
        zooKeeperLock.runTest();
    }

    //1.获取ZooKeeper连接
    public ZkClient zkClient = new ZkClient("127.0.0.1:2181");

    //2.信号量,通过信号量来设置线程的阻塞与唤醒
    public CountDownLatch countDownLatch = null;

    //3.获取锁(也就是向Zookeeper上创建一个临时节点
    //创建临时节点不会返回数据,如果创建失败会报错
    //如果报错说明创建失败返回false)
    public boolean tryLock() {
        try {
            zkClient.createEphemeral("/LOCK");
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    //3.获取锁失败时的阻塞方法
    public void waitLock() {

        //1.创建IZkDataListener事件对象,需要重写handleDataDeleted()
        //与handleDataChange() 方法,在第一执行当前方法时并不是执行内部的
        //方法,只是先初始化IZkDataListener,直接进入了下面的第2步
        //当被监听的节点触发监听事件时,自动调用重写的方法
        IZkDataListener izkDataListener = new IZkDataListener() {
            //删除监听节点时回调执行的方法
            //当执行该方法时说明前面获取到锁的线程已经执行完毕释放了锁(也就是关闭
            //Zookeeper连接,删除了临时节点,触发了监听事件
            public void handleDataDeleted(String path) throws Exception {
                if (countDownLatch != null) {
                    //调用countDown()方法,将信号量累减为0(刚开始是1)
                    //唤醒后续获取锁失败等待的线程
                    countDownLatch.countDown();
                }
            }
            //修改监听节点时回调执行的方法
            public void handleDataChange(String path, Object data) throws Exception {
            }
        };


        //2.调用subscribeDataChanges() 方法,开启监听
        zkClient.subscribeDataChanges("/LOCK", izkDataListener);

        //3.判断其它线程是否释放了锁,也就是判断ZooKeeper上代表锁的节点是否还存在
        if (zkClient.exists("/LOCK")) {
            //如果存在初始化信号量对象并设置为1
            countDownLatch = new CountDownLatch(1);
            try {
                //信号量调用await()方法,判断信号量的值,如果不为0则阻塞等待
                //当信号量值变为0时,当前阻塞等待的线程会自动由此处开始继续向下执行
                countDownLatch.await();
                System.out.println("放开阻塞继续执行");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //删除监听,(不删除会影响执行效率,注意该方法在if外部)
        //监听是在第2步骤开启的,此处删除并不影响后续锁的获取与释放
        //在由线程进来如果获取锁失败,在第2步骤开启监听后,第3步骤就阻塞了
        zkClient.unsubscribeDataChanges("/LOCK", izkDataListener);
    }

    //4.释放锁
    public void unLock() {
        if (zkClient != null) {
            zkClient.close();
            System.out.println("释放锁资源...");
        }
    }


    //5.逻辑代码整合在调用逻辑代码前首先获取锁,如果获取成功执行
    //需要加锁的逻辑代码,失败则阻塞等待
    public void runTest() throws InterruptedException {
        //1.获取锁
        boolean b = tryLock();

        //2.判断锁释放获取成功,成功则执行加锁逻辑代码
        //代码执行完毕,释放锁
        if (b) {
            System.out.println("获取锁成功,需要加锁执行的逻辑代码执行----->");
            Thread.sleep(10000);
            System.out.println("加锁逻辑代码执行完毕,开始释放锁----->");
            //3.释放锁
            unLock();
        } else {
            //4.获取锁失败,在此处进行阻塞等待
            waitLock();

            //当上一个获取锁的线程执行完毕后,释放锁
            //此处获取锁失败阻塞的线程被唤醒继续执行(是在监听事件中唤醒的)
            //递归调用当前的 runTest()方法,再次尝试获取执行
            runTest();
        }
    }

    //test方法测试开启第二个线程去获取锁执行业务
    @Test
    public void test() throws InterruptedException {
        ZooKeeperLockTest zooKeeperLock = new ZooKeeperLockTest();
        zooKeeperLock.runTest();
    }

    @Test
    public void test2() throws InterruptedException {
        zkClient.delete("/LOCK");
    }

    @Test
    public void test3() {
        zkClient.close();
    }

}

ZooKeeper 实现分布式锁需要考虑的问题

  1. 羊群效应: 基于zk的临时节点实现分布式锁,当获取锁的线程执行完毕释放锁后,会唤醒等待的所有线程再次去争抢锁资源,某个线程获取锁成功后其它线程再次阻塞等待,这个唤醒所有线程,就是羊群效应
  2. 通过zk临时序列节点实现分布式锁,解决羊群效应
  • 利用zk临时序列节点的特性,创建同名节点zk会根据创建时间自动在节点nam上添加序列后缀例如"/lock/name0001"
  • 多线程获取锁时在zk指定/lock节点下创建临时序列节点,创建成功后,获取"/lock"节点下的所有子节点,判断当前线程创建的节点是否是第一个,如果是获取锁成功放行
  • 如果不是第一个子节点,则说明锁被其它线程获取,获取该节点的上一个节点,对该节点的上一个节点开启监听,当前线程countDownLatch.await()阻塞等待
  • 当获取到锁的线程执行完毕后,删除该线程在zk上创建的临时节点,由于只有一个线程对当前节点开启了监听,在唤醒线程时也只会唤醒对当前节点开启监听的一个线程
  1. zk 脑裂问题: 由于网络原因,zk集群环境判断当Leader节点宕机,选举Follower节点升级为Leader,在极端情况下,当前选举出的Follower节点中被没有存储当前正在执的线程获取到锁的节点,此时再有其它线程执行,也会获取锁成功,此时就有两个线程同步执行,解决这个问题,在存储临时节点时附加一个唯一序号或id,其它线程再创建临时节点时与上一个节点序号解析比对,如果累计加1等于当前节点的则说明没有出现以上问题,如果不是则说明出现了脑裂
    Zookeeper3.4.6的选举算法是FastLeaderElection,该算法的规则是投票超过半数的服务器才能当选为Leader。这个算法能够保证leader的唯一性

三. Curator

  1. Curator是Netflix公司开源的一套zookeeper客户端框架,解决了很多Zookeeper客户端非常底层的细节开发工作,包括连接重连、反复注册Watcher和NodeExistsException异常等等(参考大神的博客)

添加链接描述
添加链接描述
添加链接描述
添加链接描述
添加链接描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ZooKeeper 实现分布式锁的简单示例 的相关文章

  • 使用现有同级属性值对属性进行 Jackson 多态反序列化

    我有一个现有的Request Response协议使用JSON我无法控制 示例1 响应JSON不需要任何多态反序列化 name simple response params success true 示例2 响应JSON需要对 params
  • 如何测试两个 Joda-Time DateTime 对象几乎相等?

    在单元测试中 我经常使用返回DateTime于或关于now 有没有办法说actual日期时间在几秒之内actual约会时间 这听起来是个坏主意 单元测试不应该以任何方式依赖于当前的实际时间 这就是为什么注入一些接口是一个很好的做法 称为Cl
  • 您最好的 Swing 设计模式和技巧是什么? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 如何使用 selenium 和 junit 测试多个浏览器(版本)

    我刚刚发现了硒 一个很棒的工具 我计划运行 使用 selenium ide 生成的 junit4 代码 但我需要它与许多浏览器 网络驱动程序一起运行 这个用例有 junit java 模式吗 我的第一个想法是使用 RunWith Param
  • 如何在 Android 上将 ISO 8601 字符串解析为 Java 日期 [重复]

    这个问题在这里已经有答案了 我正在 Android 上创建一个与服务器通信的应用程序 该服务器给我返回了一个ISO 8601 http en wikipedia org wiki ISO 8601日期字符串 如下所示 2014 11 21
  • JavaFX 控制器如何访问其他服务?

    我将 JavaFX 2 与 Scala 一起使用 我有class Application extends javafx application Application它执行诸如读取应用程序配置等操作 然后它会启动主窗口 该主窗口需要连接到一
  • 如何使用 DirectoryStream.Filter 过滤隐藏文件

    我正在尝试使用 NIO 类过滤隐藏文件 当我在 Windows 10 上运行随附的代码时 我得到以下输出 Files c Documents and Settings c PerfLogs c Program Files c Program
  • OpenGL ES 2.0 只绘制一次对象

    首先我要说的是 很抱歉今天问了这么多问题 所以 我有一个圈子的课程 我有一个包含 3 个圆形实例的数组列表 每个实例都有不同的 x 坐标来绘制 不管出于什么原因 OpenGL ES 2 0 只绘制其中之一 尽管我调用所有这些来绘制 我检查了
  • 使用具有不同参数的 Jackson for List 将 JSON 映射到 pojo

    JSON 格式 0 cast showname woh pagle type Episodes video src video mp4 DRM False 这里的问题是我遇到以下异常 org codehaus jackson map Jso
  • 在 IntelliJ IDEA 中编辑并继续?

    使用 IntelliJ IDEA 社区版进行调试时是否可以编辑一些代码 我在选项中找不到这个功能 是的 这就是所谓的 热插拔 您可以在调试过程中编译修改后的代码 并且类文件将被替换 直到您停止调试 确保在调试器设置中启用 HotSwap 选
  • Jackson - 将值传递给 JsonDeserializer

    我有一个现有的类层次结构 如下所示 public interface Service String getId String getName public class FooTask extends AbstractTask private
  • Java.lang.NoClassDefFoundError:com/fasterxml/jackson/databind/exc/InvalidDefinitionException

    我已经更新了我的依赖项 就像您在评论中所说的那样 我现在有这个 org springframework context ApplicationContextException Unable to start embedded contain
  • 从文件夹中读取java文件

    我开发了一个应用程序 可以从用户选择的文件夹中读取文件 它显示每个文件中有多少行代码 我只想在文件选择器中显示 Java 文件 具有 java 扩展名的文件 下面是我的代码 public static void main String ar
  • 我的递归条件是否正确计算二叉树高度?

    我想在你的帮助下知道我的代码是对还是错 因为遗憾的是我无法运行它来检查 没有编译错误 我想做的是找到二叉树的高度 当然 树不必是平衡的 二叉树中的每个节点可以有两个节点作为子节点 http en wikipedia org wiki Bin
  • Spring MVC @RequestBody 不适用于 jquery ajax?

    这是我的ajax请求 var dataModel name1 value1 name2 value2 ajax url testURL type POST async false contentType application json d
  • 从 java 反射中隐藏我的安全密钥

    下面的类是我用于加密的安全密钥提供程序 public class MySecretKey private String key 2sfdsdf7787fgrtdfg cj5 Some Util methods goes on Here 首先
  • 找出网络上所有活动机器的IP

    如何找到 LAN 上所有当前活动计算机的 IP 如何编写一个可以在任何子网上运行的通用程序 我目前正在这样做 尝试 isReachable 是否到达我子网上的所有机器 如果他们这样做 请存储他们的 IP 地址 无论如何 是否有其他方法可以手
  • Java 中使用 PBKDF2 进行密码验证

    我正在用 Java 进行基于密码的文件加密 我使用 AES 作为底层加密算法PBKDF2WithHmacSHA1使用以下代码从盐和密码组合中派生密钥 我从本网站上的另一位慷慨的海报获得 SecretKeyFactory f SecretKe
  • java 未知深度的嵌套哈希图

    我有一个要求 我需要有一个嵌套的哈希图 但深度将在运行时决定 例如 如果在运行时 用户说 3 那么我的哈希图应该是这样的 HashMap
  • 将 JVM 参数放入要在运行时获取的文件中

    我正在构建当前应用程序的 jar 它需要设置几个 JVM 参数 有没有办法在文件中而不是在命令行上设置这些 JVM 参数 我已经做了一些搜索 看起来我可以使用 java properties 文件做一些事情 可能通过设置 java args

随机推荐

  • Econometrics/Stata再学习(一)

    Tips 1 For large data set memory 2 price index 长时间数据要注意平价 3 在做调查之前 一定要先思考想要什么样的图 是否又方差差异 大体趋势的形式 再去设计问卷问题 调查策略 从相关性到因果关系
  • 通过java实现微信公众号发送微信消息

    感谢好多老哥提供的文档 哈哈 这里我就只是代码贴出来 方便以后copy 1获取token String token Wechat getAccess token appId appSecret getString access token
  • repost: mysql之row_format、溢出页(overflow pages)、mysql数据类型(varchar、text、blob、json)

    repost https blog csdn net aecuhty88306453 article details 102196591 MySQL数据行 row format 溢出的深入理解 在 mysql中 若一张表里面不存在varch
  • mysql 函数使用

    1 GROUP CONCAT 功能 将group by产生的同一个分组中的值连接起来 返回一个字符串结果 语法 group concat distinct 要连接的字段 order by 排序字段 asc desc separator 分隔
  • 苹果APP安装包ipa如何安装在手机上

    苹果APP安装包ipa如何安装在手机上 苹果APP的安装比安卓复杂且困难 很多人不知道如何将ipa文件安装到手机上 以下是几种苹果APP安装在iOS设备的方式 供大家参考 一 上架App Store 这是最正规的方式 虽然审核过程复杂 时间
  • idea 部署SpringBoot项目时打成jar包一些坑的总结

    前一段时间 公司分配给我做一些微服务的功能 比如一些分析 一些可以拿出来的模块 所以采用了SpringBoot搭建的微服务项目 至于关于SpringBoot的介绍 这里就不展开了 今天主要写的是这两天我需要把我本地的项目部署到服务器上 遇到
  • HTML输入框标签

    1
  • 华院计算|切比雪夫,他带起了俄罗斯现代数学的发展

    俄罗斯的数学家们常说 他们的现代数学是由切比雪夫带动而建立和发展起来的 图1 帕夫努蒂 切比雪夫 1869年 帕夫努蒂 切比雪夫 Pafnuty Lvovich Chebyshev 1821年5月16 日 1894年12月8日 出生于离莫斯
  • 编译ORBSLAM2中遇到的一些问题

    从github中下载源码后 按照readme一步步来 安装完各种依赖后 直接运行了build sh 需要注意的是 之前在14 04中安装完依赖后 cmake报错 根据错误信息锁定为cmake版本低 于是重装了cmake 3 9 成功编译 后
  • java 判断当天_java判断一个时间是否是今天的方法

    java判断时间是否是今天 public class Test java 判断一个时间是不是今天的时间范围内 param args public static void main String args String time 2017 0
  • [计算机毕业设计]基于OpenCV的图像梯度与边缘检测

    目录 前言 课题背景与意义 课题实现技术思路 一 图像梯度与几种算子 三 基于OpenCV的实现 最后 前言 大四是整个大学期间最忙碌的时光 一边要忙着准备考研 考公 考教资或者实习为毕业后面临的就业升学做准备 一边要为毕业设计耗费大量精力
  • LinkedHashSet和LinkedHashMap手记

    LinkedHashSet和LinkedHashMap手记 LinkedHashSet和LinkedHashMap是Java集合框架中的两个重要类 它们是HashSet和HashMap的变体 它们在维护插入顺序方面提供了额外的功能 这使得它
  • WVP-PRO+ZLMediaKit搭建GB28181视频平台(linux详细教学)

    文章目录 一 安装WVP PRO 1 源码下载 链接内任选其一 https doc wvp pro cn 2 修改配置 仔细查看每行都有说明注释 3 编译前端页面 4 打包为jar 上传到服务器 二 安装ZLMediaKit 1 前置环境安
  • Android10 Settings系列(五)恢复出厂设置添加重启和关机按钮,增加恢复出厂设置电量限制

    一 前言 这个应该算是一个通用需求 因为源码中的恢复出厂设置确实只有关机 没有恢复出厂设置之后重启的功能 二 准备工作 恢复出厂设置这个找到对应的类很简单 这里直接给出对应的处理类 packages apps Settings src co
  • jaspersoft studio6.x 设计医院检查报告单样式

    今天百度无意检索到医院的检查报告单 我想尝试一下自己对jaspersoft studio6 x 报表工具的掌握情况 我简单编写了一个血液检查单 效果展示如下 百度图片库关于血液检查报告单样式 jaspersoft studio 设计截图 源
  • D3D纹理

    纹理映射是一种将图形施加到表面的技术 以简单的一堵墙为例 这种技术可以只需要两个绘制有砖纹理的三角形即可 这样就可以为表面增加大量的细节 而不必使用大量的多边形 纹理映射使用了图像数据并将图像数据绘制 映射 到表面上 该表面看上去就像有一幅
  • KubeSphere使用GlusterFS作为持久化存储

    文章目录 一 前言 1 主机准备 2 准备磁盘 二 安装glusterfs服务端 1 配置glusterfs yum源 2 安装gluster服务 3 开启服务 并做开机启动 4 glusterfs的端口 三 安装Heketi 服务 实现k
  • 解你燃眉之急——悟空CRM9.0懒人版安装搭建--Good Luck,助你成功

    CRM悟空懒人版搭建 环境准备 1 安装jdk 查看版本 2 安装tomcat tomcat有版本要求 要9及以上 需要和CRM9结合 启动 查验端口 3 安装mysql 这里必须安装mysql5 7版本的 适应悟空CRM 这里需要注意的是
  • 7-flutter Navigator 和Route

    Route 和 Navigator 用于页面之间的跳转 一 Navigator 的 push 和 pop 用于页面之间的跳转 创建MaterialApp时可以指定routes参数 该参数是一个映射路由名称和构造器的Map 跳转的时候 使用
  • ZooKeeper 实现分布式锁的简单示例

    目录 一 分布式锁概述 二 ZooKeeper 实现分布式锁 ZooKeeper 实现分布式锁原理 实现分析 代码示例 ZooKeeper 实现分布式锁需要考虑的问题 三 Curator 一 分布式锁概述 在分布式环境中 服务器集群 多个j