使用ZooKeeper实现Java跨JVM的分布式锁

使用ZooKeeper实现分布式锁 在分布式高并发的情况下,多个访问之间拥有唯一的一个订单编号,多次提交的订单不会出现重复的,只能是唯一的,ZooKeeper就很好的解决了唯一性 立即下载

说明:本文是使用Curator框架进行讲解及演示,Curator是对Zookeeper客户端的一个封装,因为Zookeeper的客户端实现偏底层,如果想要实现锁或其他功能都需要自己封装,实现一些简单的功能还可以,如果想要实现锁这种高并发下的东西,不建议自己封装,除非你自信你写的东西比国外大神写的还好~ 如果是研究学习到是可以自己写一下,同时也可以看看开源的代码,那里面还是有很多值得学习的东西。

Zookeeper版本为 Release 3.4.8(stable)
Curator版本为2.9.1

[java] view plain copy 在CODE上查看代码片派生到我的代码片

org.apache.zookeeper
zookeeper
3.4.8


org.apache.curator
curator-recipes
2.9.1


org.apache.curator
curator-client
2.9.1

锁原理:

1、首先要创建一个锁的根节点,比如/mylock。

2、想要获取锁的客户端在锁的根节点下面创建znode,作为/mylock的子节点,节点的类型要选择CreateMode.PERSISTENT_SEQUENTIAL,节点的名字最好用uuid(至于为什么用uuid我后面会讲,先说一下~如果不这么做在某种情况下会发生死锁,这一点我看了很多国内朋友自己的实现,都没有考虑到这一层,这也是我为什么不建议大家自己去封装这种锁,因为它确实很复杂),假设目前同时有3个客户端想要获得锁,那么/mylock下的目录应该是这个样子的。

xxx-lock-0000000001,xxx-lock-0000000002,xxx-lock-0000000003
xxx为uuid , 0000000001,0000000002,0000000003 是zook服务端自动生成的自增数字。

3、当前客户端通过getChildren(/mylock)获取所有子节点列表并根据自增数字排序,然后判断一下自己创建的节点的顺序是不是在列表当中最小的,如果是 那么获取到锁,如果不是,那么获取自己的前一个节点,并设置监听这个节点的变化,当节点变化时重新执行步骤3 直到自己是编号最小的一个为止。

举例:假设当前客户端创建的节点是0000000002,因为它的编号不是最小的,所以获取不到锁,那么它就找到它前面的一个节点0000000001 并对它设置监听。

4、释放锁,当前获得锁的客户端在操作完成后删除自己创建的节点,这样会激发zook的事件给其它客户端知道,这样其它客户端会重新执行(步骤3)。

举例:加入客户端0000000001获取到锁,然后客户端0000000002加入进来获取锁,发现自己不是编号最小的,那么它会监听它前面节点的事件(0000000001的事件)然后执行步骤(3),当客户端0000000001操作完成后删除自己的节点,这时zook服务端会发送事件,这时客户端0000000002会接收到该事件,然后重复步骤3直到获取到锁)

上面的步骤实现了一个有序锁,也就是先进入等待锁的客户端在锁可用时先获得锁。

如果想要实现一个随机锁,那么只需要把PERSISTENT_SEQUENTIAL换成一个随机数即可。

简单示例:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
package com.framework.code.demo.zook;

import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class CuratorDemo {

public static void main(String[] args) throws Exception {  

    //操作失败重试机制 1000毫秒间隔 重试3次  
    RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);  
    //创建Curator客户端  
    CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.1.18:2181", retryPolicy);  
    //开始  
    client.start();  

    /** 
     * 这个类是线程安全的,一个JVM创建一个就好 
     * mylock 为锁的根目录,我们可以针对不同的业务创建不同的根目录 
     */  
    final InterProcessMutex lock = new InterProcessMutex(client, "/mylock");  
    try {  
        //阻塞方法,获取不到锁线程会挂起。  
        lock.acquire();  
        System.out.println("已经获取到锁");  
         Thread.sleep(10000);  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  
    finally{  
        //释放锁,必须要放到finally里面,已确保上面方法出现异常时也能够释放锁。  
        lock.release();  
    }  

    Thread.sleep(10000);  

    client.close();  
}  

}

上面代码再获取锁的地方暂停了10秒钟,我们使用zook的客户端去查看目录的创建情况,由于我前面已经做了几次测试,所以序号是从12开始的。

模拟多个客户端(也可以认为是多个JVM):

现在把上面的代码改造一下放入到线程中去执行,模拟多个客户端测试。

[java] view plain copy 在CODE上查看代码片派生到我的代码片
public class CuratorDemo {

public static void main(String[] args) throws Exception {  
    for (int i = 0; i < 10; i++) {  
        //启动10个线程模拟多个客户端  
        Jvmlock jl = new Jvmlock(i);  
        new Thread(jl).start();  
        //这里加上300毫秒是为了让线程按顺序启动,不然有可能4号线程比3号线程先启动了,这样测试就不准了。  
        Thread.sleep(300);  
    }  
}  

public static class Jvmlock implements Runnable{  

    private int num;  
    public Jvmlock(int num) {  
        this.num = num;  
    }  

    @Override  
    public void run() {  
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);  
        CuratorFramework client = CuratorFrameworkFactory  
                .newClient("192.168.142.128:2181", retryPolicy);  
        client.start();  

        InterProcessMutex lock = new InterProcessMutex(client,  
                "/mylock");  
        try {  
            System.out.println("我是第" + num + "号线程,我开始获取锁");  
            lock.acquire();  
            System.out.println("我是第" + num + "号线程,我已经获取锁");  
            Thread.sleep(10000);  
        } catch (Exception e) {  
            e.printStackTrace();  
        } finally {  
            try {  
                lock.release();  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
        }  
        client.close();  
    }  
}  

}

通过客户端软件我们可以看到10个申请锁的节点已经被创建出来了。

看一下打印结果,先申请获取锁的线程在锁可用时最先获取到锁,因为他们申请锁时创建节点的顺序号是递增的,先申请锁的客户端创建的节点编号最小,所以先获取到锁

[java] view plain copy 在CODE上查看代码片派生到我的代码片
我是第0号线程,我开始获取锁
我是第0号线程,我已经获取锁
我是第1号线程,我开始获取锁
我是第2号线程,我开始获取锁
我是第3号线程,我开始获取锁
我是第4号线程,我开始获取锁
我是第5号线程,我开始获取锁
我是第6号线程,我开始获取锁
我是第7号线程,我开始获取锁
我是第8号线程,我开始获取锁
我是第9号线程,我开始获取锁
我是第1号线程,我已经获取锁
我是第2号线程,我已经获取锁
我是第3号线程,我已经获取锁
我是第4号线程,我已经获取锁
我是第5号线程,我已经获取锁
我是第6号线程,我已经获取锁
我是第7号线程,我已经获取锁
我是第8号线程,我已经获取锁
我是第9号线程,我已经获取锁

为什么节点的名称要加上uuid,这是框架的英文解释。

It turns out there is an edge case that exists when creating sequential-ephemeral nodes. The creation can succeed on the server, but the server can crash before the created node name is returned to the client. However, the ZK session is still valid so the ephemeral node is not deleted. Thus, there is no way for the client to determine what node was created for them.

Even without sequential-ephemeral, however, the create can succeed on the sever but the client (for various reasons) will not know it.

Putting the create builder into protection mode works around this. The name of the node that is created is prefixed with a GUID. If node creation fails the normal retry mechanism will occur. On the retry, the parent path is first searched for a node that has the GUID in it. If that node is found, it is assumed to be the lost node that was successfully created on the first try and is returned to the caller.

就是说 当客户端创建了一个节点,这个创建的过程在zook的服务器端已经成功了,但是在将节点的路径返回给客户端之前服务器端挂了, 因为客户端的session还是有效的,所以这个节点不会删除, 这样客户端就不知道哪个节点是它创建的。

当客户端发生创建失败的时候,会进行重试,如果这个时候zook已经恢复可用,那么客户端会查询服务器端所有子节点,然后通过和自己创建的uuid对比,如果找到了,说明这个节点是它之前创建的,那么久直接使用它,不然这个节点就会成为一个死节点,导致死锁。

实现非公平锁:

重写创建节点的方法,

[java] view plain copy 在CODE上查看代码片派生到我的代码片
package com.framework.code.demo.zook.lock;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.StandardLockInternalsDriver;
import org.apache.zookeeper.CreateMode;

public class NoFairLockDriver extends StandardLockInternalsDriver {

/** 
 * 随机数的长度 
 */  
private int numLength;   
private static int DEFAULT_LENGTH = 5;  

public NoFairLockDriver() {  
    this(DEFAULT_LENGTH);  
}  

public NoFairLockDriver(int numLength) {  
    this.numLength = numLength;  
}  

@Override  
public String createsTheLock(CuratorFramework client, String path, byte[] lockNodeBytes) throws Exception  
{  
    String newPath = path + getRandomSuffix();  
    String ourPath;  
    if ( lockNodeBytes != null )  
    {  
        //原来使用的是CreateMode.EPHEMERAL_SEQUENTIAL类型的节点  
        //节点名称最终是这样的_c_c8e86826-d3dd-46cc-8432-d91aed763c2e-lock-0000000025  
        //其中0000000025是zook服务器端资自动生成的自增序列 从0000000000开始  
        //所以每个客户端创建节点的顺序都是按照0,1,2,3这样递增的顺序排列的,所以他们获取锁的顺序与他们进入的顺序是一致的,这也就是所谓的公平锁  
        //现在我们将有序的编号换成随机的数字,这样在获取锁的时候变成非公平锁了  
        ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL).forPath(newPath, lockNodeBytes);  
        //ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path, lockNodeBytes);  
    }  
    else  
    {  
        ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL).forPath(newPath);  
        //ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path);  
    }  
    return ourPath;  
}  

/** 
 * 获得随机数字符串 
 */  
public String getRandomSuffix() {  
    StringBuilder sb = new StringBuilder();  
    for (int i = 0; i < numLength; i++) {  
        sb.append((int) (Math.random() * 10));  
    }  
    return sb.toString();  
}  

}

把我们写的类注册进去:

[java] view plain copy 在CODE上查看代码片派生到我的代码片
InterProcessMutex lock = new InterProcessMutex(client,”/mylock”, new NoFairLockDriver());

还是上面的例子,在跑一边看结果,可以看到,获取锁的顺序已经是无序的了,从而实现了非公平锁。
[java] view plain copy 在CODE上查看代码片派生到我的代码片
我是第1号线程,我开始获取锁
我是第0号线程,我开始获取锁
我是第0号线程,我已经获取锁
我是第2号线程,我开始获取锁
我是第3号线程,我开始获取锁
我是第4号线程,我开始获取锁
我是第5号线程,我开始获取锁
我是第6号线程,我开始获取锁
我是第7号线程,我开始获取锁
我是第8号线程,我开始获取锁
我是第9号线程,我开始获取锁
我是第9号线程,我已经获取锁
我是第8号线程,我已经获取锁
我是第4号线程,我已经获取锁
我是第7号线程,我已经获取锁
我是第3号线程,我已经获取锁
我是第1号线程,我已经获取锁
我是第2号线程,我已经获取锁
我是第5号线程,我已经获取锁
我是第6号线程,我已经获取锁
转自:http://blog.youkuaiyun.com/nimasike/article/details/51567653

基于zookeeper的分布式锁实现demo 基于zookeeper的分布式锁实现demo,可以直接用在生产环境 立即下载

相关推荐

STM32实战:5分钟搞定MXL90614红外测温模块的I2C驱动(附完整代码)

本文提供了一份快速集成MLX90614红外测温模块的STM32实战指南。通过详细的软件I2C驱动代码,重点解析了SMBus通信协议、CRC校验及温度数据转换,帮助开发者避开常见陷阱,实现5分钟内完成模块驱动并读取准确温度值。

wasm7browser的博客 353

【使用zookpeer】模拟 hadoop的 datenode与namenode 的master-slaves的 关系

功能: 当 datanode上线时,namenode中可以感知到. 当datanode下线时,namenode中可以感知到. 分析: 当datanode上线时,datanode在zk中创建的节点类型为 瞬时的顺序节点.  2. namenode临听节点的变化情况,一旦有一个datanode下线,则这个节点会删除,namenode会自动回调》 请用对象流完成 datanode的 信息的注册. datanode的节点信息: System中获取 properties. 前提: 利用zk完成,请先在z

qq_49032728的博客 771

别再手动量了!用Python+Open3D给BIM模型做‘CT扫描’,自动揪出施工误差(附完整代码)

本文介绍如何利用Python和Open3D技术实现BIM模型的毫米级施工误差自动检测。通过点云预处理、模型配准和偏差分析,系统能自动生成三维差异热力图和验收报告,显著提升施工质量检测效率。特别适合混凝土结构错位和钢结构安装偏差等常见问题的智能分析。

weixin_29284657的博客 244

Java分布式锁理论(redis、zookeeper) 详解

1、定时任务2、秒杀抢购,防止库存超卖的问题3、双写一致性协议比如我们为了高可用性搭建了服务集群,分别是8080和8081,我们在项目中设立定时任务,目的是每天晚上定时拉取用户数据,给每个人发送一些推荐短信。那么这会出现什么问题呢?8080和8081都有定时任务,到半夜2点同时查询数据库,同时调用阿里云接口发短信,那么肯定会重复,使用了分布式锁,8080抢到锁执行定时任务,那么8081就会阻塞不会执行。那么肯定会有人问,为什么不用synchronized锁呢?

qq_38196449的博客 1635

高缺失率时空数据插补实战:从随机森林到扩散模型的性能对比

数据缺失是数据分析与机器学习建模中普遍面临的挑战,尤其在时空数据领域,传感器故障、传输中断等问题常导致数据不完整。数据插补技术旨在基于已知数据的模式和关联性,合理估算缺失值,为后续分析提供可靠基础。其核心原理在于利用数据的时空相关性(如时间周期性、空间邻近性)及与外部驱动因素(如交通、气象)的关联构建预测模型。这项技术的价值在于能将不完整的数据集转化为可用于高质量分析和预测的可用资产,广泛应用于环境监测、交通流量预测、金融风控等领域。本文聚焦于一个极具挑战性的场景——PM2.5监测数据缺失率高达82%,通过

weixin_30363285的博客 545

基于STM32的智能垃圾分类机器人

垃圾分类是城市管理的重要环节,传统人工分拣效率低、成本高。本文设计了一款基于STM32的智能垃圾分类机器人,通过视觉识别与机械臂协同作业,实现垃圾自动分类与投放,适用于社区、公共场所等场景,可显著提升垃圾分类效率。:STM32H743(480MHz Cortex-M7,支持AI加速)本系统实现了垃圾分拣的自动化,显著提升了分类效率。OV5640摄像头(500万像素,支持JPEG输出)支持4类垃圾识别(可回收/厨余/有害/其他)问题讨论,stm32的资料领取可以私信!超声波避障(探测距离0.1-4m)

stm32d1219的博客 1821

深入探索Java中的分布式锁服务与Zookeeper集成

通过本文,我们介绍了如何在Java中使用Zookeeper实现分布式锁服务。Zookeeper的强一致性和高可用性使其成为实现分布式锁的理想选择。希望通过本文的讲解和示例代码,能够帮助你在分布式系统中更好地管理资源竞争问题。如果你对其他分布式锁实现感兴趣,可以参考表格中的对比,选择适合你业务场景的方案。

weixin_53840353的博客 1416

洛谷P5707 【深基2.例12】上学迟到(c语言)

第一篇题解 题目描述 yyy 的学校要求早上 8 点前到达。学校到 yyy 的家一共有 s(s\le 10000)s(s≤10000) 米,而 yyy 可以以 v(v<10000)v(v<10000) 米每分钟的速度匀速走到学校。此外在上学路上它还要额外花 10 分钟时间进行垃圾分类。请问为了避免迟到 yyy 最晚什么时候出门?输出 HH:MM 的时间格式,不足两位时补零。由于路途遥远, yyy 可能不得不提前一天出发,不过不可能提前超过一天。 输入格式 两个正整数 s,v,意思已经在题目中给定

cc_love_program的博客 1万+

ZooKeeper分布式锁实现java例子,附完整可运行源代码

本原创入门教程,涵盖ZooKeeper核心内容,通过实例和大量图表,结合实战,帮助学习者理解和运用,任何问题欢迎留言。 目录: zookeeper介绍与核心概念 安装和使用 ZooKeeper分布式锁实现 ZooKeeper框架Curator分布式锁实现及源代码分析 zookeeper 开发实战(java客户端) ------------------------------------...

稀有气体的技术博客 1万+

c\c++: Identifiers, modifier, qualifier, specifier, decorator; systemtap

文章目录参考英语含义在c、C++ 参考 c++ 标准 https://blog.youkuaiyun.com/tyronne/article/details/44016621 英语含义 modifier word or phrase that modifies (modify 2) another word or phrase 修饰语. qualifier (grammar) word, esp an adjective or adverb, that qualifies another word 修饰词; (尤指)形

mzhan017的博客 2432

基于深度学习的鸟类检测识别系统:YOLOv5 + YOLOv8 + YOLOv10 + UI界面 + 数据集

YOLOv5是当前目标检测领域非常流行的模型,具有良好的检测精度和实时推理速度。其网络结构相比前几代YOLO模型进行了优化,尤其是在检测精度和计算效率上有了显著提高。YOLOv5特点高效性:能够在较低的硬件环境下进行实时检测。易于部署和使用:YOLOv5提供了简单的API接口,方便开发者快速集成和使用。优异的精度:在多个数据集上测试,表现出色。本文详细介绍了基于YOLOv5、YOLOv8和YOLOv10的鸟类检测识别系统的设计与实现。

YOLO 1115

使用ZooKeeper实现Java跨JVM的分布式锁(优化构思)

说明:这篇文章是基于 使用ZooKeeper实现Java跨JVM的分布式锁 的,没有阅读的朋友请先阅读前面的文章后在阅读本文。 上一篇文章中介绍了如何使用分布式锁,并且对原来的公平锁进行了扩展,实现了非公平锁,已经能够满足大部分跨进程(JVM)锁的需求了。 问题:我们都知道在单个JVM内部实现锁的机制很方便,Java也提供了很丰富的API可以实现,例如Synchronized关键字,

大新博客 1万+

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

使用ZooKeeper实现Java跨JVM的分布式锁(读写锁)

一、使用ZooKeeper实现Java跨JVM的分布式锁 二、使用ZooKeeper实现Java跨JVM的分布式锁(优化构思) 三、使用ZooKeeper实现Java跨JVM的分布式锁(读写锁) 读写锁: 本文在前面俩片的基础之上介绍如何使用ZooKeeper实现Java跨JVM的分布式锁(读写锁)。 简单介绍一下读写锁,在使用读写锁时, 多个客户端(线程)可以同时获...

大JAVA解决方案 262

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

zookeeper java 锁_使用ZooKeeper实现Java跨JVM的分布式锁(读写锁)

读写锁:本文在前面俩片的基础之上介绍如何使用ZooKeeper实现Java跨JVM的分布式锁(读写锁)。简单介绍一下读写锁,在使用读写锁时, 多个客户端(线程)可以同时获取 “读锁”, 但是“写入锁”是排它的,只能单独获取。1、假设A,B线程获取到 “读锁”, 这时C线程就不能获取 “写锁”。2、假设C线程获取了“写锁”,那么A,B线程就不能获取“读锁”。这在某种情况下会大幅度提高系统的性能,在...

weixin_39996096的博客 86

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

java 集群锁_使用ZooKeeper实现Java跨JVM的分布式锁(读写锁)

读写锁:本文在前面俩片的基础之上介绍如何使用ZooKeeper实现Java跨JVM的分布式锁(读写锁)。简单介绍一下读写锁,在使用读写锁时, 多个客户端(线程)可以同时获取 “读锁”, 但是“写入锁”是排它的,只能单独获取。1、假设A,B线程获取到 “读锁”, 这时C线程就不能获取 “写锁”。2、假设C线程获取了“写锁”,那么A,B线程就不能获取“读锁”。这在某种情况下会大幅度提高系统的性能,在...

weixin_33439618的博客 201
上一篇: Concurrent下的线程安全集合
下一篇: Dubbo架构设计详解
wingahi
博客等级 码龄15年 15粉丝 43原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值