Zookeeper之手写一个分布式锁

前言

我之前写了一篇快速上手ZK的文章:https://blog.csdn.net/qq_38974073/article/details/135293106

本篇最要是进一步加深学习ZK,算是一次简单的实践,巩固学习成果。

设计一个分布式锁

对锁的基本要求

  • 可重入:允许同一个应用内的同一个线程重复调用同一个方法;
  • 阻塞:没有拿到锁的线程将进入阻塞。
  • 公平的:先来先得。

实现原理

使用zk作为发号器,每个线程申请锁时会创建一个临时有序节点:

  • 节点编号最小的获得锁,完成业务操作之后删除临时节点;
  • 如果不是最小编号的节点,就监听前一个节点的删除事件,并进入阻塞状态,当触发回调的事件时,唤醒阻塞线程,并重新进行获取锁操作。

锁要求实现的描述:

  • 可重入:对同一个线程,不用重复获取锁,重入计数+1即可;
  • 阻塞:利用CountDownLatch实现,当触发回调时唤醒线程;
  • 公平的:利用zk临时有序节点的特点进行排队,先到先申请锁。

问:申请到锁之后,网络中断怎么办?

  • 临时节点随客户端关闭而被删除

问:如何避免羊群效应?

  • 每个线程只监听前一个节点

关键流程

在这里插入图片描述

关键代码实现

锁的关键方法:

  • 加锁:lock
  • 解锁:unLock
  • 尝试加锁:tryLock

public boolean lock() {if(Thread.currentThread().equals(thread)) {lockCount.incrementAndGet();return true;}while (true) {if (tryLock()) {thread = Thread.currentThread();lockCount.incrementAndGet();return true;}try {await();} catch (Exception e) {throw new RuntimeException(e);}}
}public synchronized boolean unlock() {if (!thread.equals(Thread.currentThread())) {return false;}int newLockCount = lockCount.decrementAndGet();if (newLockCount < 0) {throw new IllegalMonitorStateException("重入锁计数不可为负数" );}// 是否剩余重入次数if (newLockCount != 0) {return true;}// 到这一步,意味着lockCount已经为0,可以删除临时节点了try{if(client.isNodeExist(properties.getZkPath())) {client.deleteNode(lockedPathMap.get(thread));}} catch (Exception e) {return false;} finally {lockedPathMap.remove(thread);priorPathMap.remove(thread);}return true;
}protected boolean tryLock() {String lockedPath = lockedPathMap.get(Thread.currentThread());if (null == lockedPath || !client.isNodeExist(lockedPath)) {lockedPathMap.put(Thread.currentThread(), lockedPath = client.createEphemeralSeqNode(getLockPrefix()));}// 取得加锁的排队编号String lockedShortPath = getShorPath(lockedPath);List<String> waiters = getWaiters();// 如果自己是所有等待锁中的第一个,则获得锁if (checkLocked(waiters, lockedShortPath)) {return true;}// 当前线程节点是否在排队int index = Collections.binarySearch(waiters, lockedShortPath);if(index < 0) {throw new NullPointerException("可能网络抖动,连接断开,临时节点失效");}// waiters最后面的节点写入map,用来监听priorPathMap.put(Thread.currentThread(), getLockPrefix() + waiters.get(index - 1));return false;
}private boolean await() throws Exception {String priorPath = priorPathMap.get(Thread.currentThread());if (null == priorPath) {throw new NullPointerException("prior_path error");}final CountDownLatch latch = new CountDownLatch(1);// 删除事件Watcher w = watchedEvent -> {// 监测到前一个节点发生变化,接下来就可以唤起等待线程,重新尝试获取锁latch.countDown();};try{// 监听前一个节点的删除时间client.watcher(w, priorPath);} catch (KeeperException.NoNodeException e) {e.printStackTrace();return false;}return latch.await(properties.getTimeout(), TimeUnit.MILLISECONDS);
}

好了,如果你对这个感兴趣,不妨拉一下完整源码: https://gitee.com/liangshij/zk-lock-demo

源码简要说明

模块说明

  • lsj-zk-lock:核心实现。
  • lsj-zk-lock-spring-boot-starter:整合springboot
  • lsj-zk-lock-test:使用demo

安装

经典三步走:导包、配置、使用

  1. 拉取代码,将lsj-zk-lock、lsj-zk-lock-spring-boot-starter通过 mvn install 命令安装到本地仓库。
  2. 引入依赖:
<dependency><groupId>cn.lsj</groupId><artifactId>lsj-zk-lock-spring-boot-starter</artifactId><version>2.4.2</version>
</dependency>

配置

  • 配置locks和dataSource:
spring:zk:dataSource:url: "localhost"port: 2181locks:- zkPath: "/test/lock"lockName: "countLock"# 获取锁失败时,进入等待的时间,等待结束将重新尝试获取锁timeout: 5000- zkPath: "/test2/lock"lockName: "lock"timeout: 5000

使用

  • 使用方式1:通过@GlobalLock注解,指定要使用那个lock
@GetMapping("test2")
@GlobalLock("countLock")
public String test2() {// 业务代码return "";
}
  • 使用方式2:通过@Qualifier注解,指定要使用那个lock
@RestController
public class TestController {int count = 0;@Resource@Qualifier("lock")private ReentrantLock lock;@Resource@Qualifier("countLock")private ReentrantLock countLock;@GetMapping("test")public String test() {countLock.lock();try{for (int i = 0; i < 10000; i++) {count++;}} finally {countLock.unlock();}return String.valueOf(count);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/585205.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

跟小德学C++之配置文件(形式)

嗨&#xff0c;大家好&#xff0c;我是出生在达纳苏斯的一名德鲁伊&#xff0c;我是要立志成为海贼王&#xff0c;啊不&#xff0c;是立志成为科学家的德鲁伊。最近&#xff0c;我发现我们所处的世界是一个虚拟的世界&#xff0c;并由此开始&#xff0c;我展开了对我们这个世界…

命令模式-举例

开关和电灯之间并不存在直接耦合关系&#xff0c;在命令模式中&#xff0c;发送者与接收者之间引入了新的命令对象&#xff0c;将发送者的请求封装在命令对象中&#xff0c;再通过命令对象来调用接收者的方法。 命令模式的主要缺点如下&#xff1a; 使用命令模式可能会导致某…

Android MVVM 写法

前言 Model&#xff1a;负责数据逻辑 View&#xff1a;负责视图逻辑 ViewModel&#xff1a;负责业务逻辑 持有关系&#xff1a; 1、ViewModel 持有 View 2、ViewModel 持有 Model 3、Model 持有 ViewModel 辅助工具&#xff1a;DataBinding 执行流程&#xff1a;View &g…

视频监控EasyCVR如何通过设置sei接口,实现在webrtc视频流中添加画框和文字?

安防视频监控系统基于视频综合管理平台EasyCVR视频系统&#xff0c;采用了开放式的网络结构&#xff0c;可以提供实时远程视频监控、视频录像、录像回放与存储、告警、语音对讲、云台控制、平台级联、磁盘阵列存储、视频集中存储、云存储等丰富的视频能力&#xff0c;具备权限管…

传奇私服教程,新手小白速速下载!

传奇私服教程&#xff0c;新手小白速速下载&#xff01; 第二十课-封玩家账号登陆-封玩家机器码登陆.zip 第十九课-快速搭建FTP服务器教程配套工具.zip 第十八课-绿盟GOM1108引擎登陆器配置防劫持列表教... 第十七课-最新访问网站自动弹出加群教程.zip 修复沙城捐献不了或者捐献…

Flutter BottomSheet 拖动分两段展示

第一段 第二段 实现思路 通过 GestureDetector 的 Drag 方法&#xff0c;动态改变Dialog的高度&#xff0c;通过设置一个最大高度和最小高度分成两层进行展示 实现 常用的展示BottomSheet的方法为 showModalBottomSheet /// 设置最高最好以高度的比例进行设置&#xff0c;方…

FPGA设计时序约束十四、Set_External_Delay

一、序言 在时序约束中对clock的约束还存在一种特殊的延时约束set external delay。set external delay如字面含义&#xff0c;设置外部的时延值&#xff0c;但这个外部时延主要是指反馈时延&#xff0c;即信号从FPGA的output端口输出后经过外部电路回到输入端口的时延值。 二…

Linux:线程优先级设置

目录 一、背景二、调整普通线程的优先级通过系统命令通过Linux C代码 三、调整实时线程的优先级通过系统命令通过Linux C代码 四、参考资料&#xff08;建议一定要阅读&#xff09; 在操作系统中&#xff0c;线程优先级决定了线程在 CPU 调度时的重要性。较高优先级的线程会在竞…

服务器被黑了,记录一下

前两天刚开的新的服务&#xff0c;立马被黑&#xff0c;哈哈。有懂这个是什么黑组织吗&#xff0c;哈哈 All your data is backed up. You must pay 0.017 BTC to 1KdmpErgS3isFf8FrLaTfGHLQPKNsv839G In 48 hours, your data will be publicly disclosed and deleted. (more i…

GEE错误——‘xxx‘ did not match any bands.

这里我们在进行影像展示的时候会出现下面的错误,主要的原因是我们虽然进行了波段运算,但是依旧无法加载,主要原因是我们没有将计算过后的波段信息进行添加到我们的一个多波段影像,这里我们首先来看看代码出现的错误提示。当然这里只是给出了主要的问题,其实在进行波段运算…

Visual Basic入门指南(一)

Visual Basic入门指南 Visual Basic是一种由微软公司开发的简单、易学的编程语言。它被广泛应用于Windows应用程序的开发&#xff0c;并且有着强大的可视化编程能力。本文将介绍Visual Basic的基础知识和常用技巧&#xff0c;帮助读者快速入门。 环境准备 随便找一个软件安装…

java在线票务系统(选座)Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java servlet 在线票务系统&#xff08;选座&#xff09;管理系统是一套完善的java web信息管理系统 系统采用serlvetdaobean&#xff08;mvc模式)&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要…

PowerShell Instal 一键部署TeamCity

前言 TeamCity 是一个通用的 CI/CD 软件平台,可实现灵活的工作流程、协作和开发实践。允许在您的 DevOps 流程中成功实现持续集成、持续交付和持续部署。 系统支持 Centos7,8,9/Redhat7,8,9及复刻系列系统支持 Windows 10,11,2012,2016,2019,2022高版本建议使用9系列系统…

从0到1快速入门ETLCloud

一、ETLCloud的介绍 ETL是将业务系统的数据经过抽取&#xff08;Extract&#xff09;、清洗转换&#xff08;Transform&#xff09;之后加载&#xff08;Load&#xff09;到数据仓库的过程&#xff0c;目的是将企业中的分散、凌乱、标准不统一的数据整合到一起&#xff0c;为企…

CentOS7 系统安装

系统下载 官方下载 清华源下载 安装流程 1. 选择安装系统 2. 选择安装语言 3. 设置网络链接 4. 设置静态IP ![img](https://img-blog.csdnimg.cn/img_convert/53bfedd54b838f95bd8bcb2efa232e23.png)设置时区 5. 磁盘设置&#xff0c;无特殊需求默认就好 6. 安装模式选择 7…

Spark应用程序的结构与驱动程序

Apache Spark是一个强大的分布式计算框架&#xff0c;用于处理大规模数据。了解Spark应用程序的结构和驱动程序是构建高效应用的关键。本文将深入探讨Spark应用程序的组成部分&#xff0c;以及如何编写一个Spark驱动程序来处理数据和执行计算。 Spark应用程序的结构 Spark应用…

cleanmymac这个软件怎么样?值不值得下载

cleanmymac是我必装的mac端清理软件&#xff0c;界面简洁好看&#xff0c;完美适配mac系统&#xff0c;文件清理的速度、精度都比较优秀&#xff0c;还是比较不错的呢。cleanmymac作为一款第三方清洁应用程序&#xff0c;具有专业完整的清理功能&#xff0c;包括释放内存、一键…

【Redis交响乐】Redis中的通用命令

文章目录 1. 基本命令 get set2. 全局命令(1)keys(2)exists(3)del(4)expire && ttl面试题: redis中key的过期策略是怎么实现的?定时器的实现原理(1)基于优先级队列/堆(2)基于时间轮实现的定时器 (5) type 我们知道,redis是按照键值对的方式存储数据的. Redis中基本的命…

根据文法求对应的语言

技巧&#xff1a;最后得到的是终结符组成的闭包 例题&#xff1a; 文法G[S]: S-->AB A-->aAb|ab B-->Bc|&#xff0c;求对应的语言 ①S-->(aAb|ab)(Bc|) ②我们可以观察到&#xff0c;无论A-->aAb还是A-->ab&#xff0c;都一定会同时出现ab,…

Java学习——设计模式——结构型模式1

文章目录 结构型模式代理模式适配器模式 结构型模式 结构型模式主要涉及如何组合各种对象以便获得更好、更灵活的结构。虽然面向对象的继承机制提供了最基本的子类扩展父类的功能&#xff0c;但结构型模式不仅仅简单地使用继承&#xff0c;而更多地通过组合与运行期的动态组合来…