Zookeeper之手写一个分布式锁

news2024/12/24 2:29:52

前言

我之前写了一篇快速上手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: 2181
    locks:
      - 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.coloradmin.cn/o/1343796.html

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

相关文章

QT/C++ 远程数据采集上位机+服务器

一、项目介绍&#xff1a; 远程数据采集与传输 课题要求:编写个基于TCP的网络数据获取与传输的应用程序; 该程序具备以下功能: 1)本地端程序够通过串口与下位机(单片机)进行通信&#xff0c;实现数据采集任务 2)本地端程序能将所获取下位机数据进行保存(如csv文本格式等); 3…

JavaWeb——前端之JSVue

接上篇笔记 4. JavaScript 概念 跨平台、面向对象的脚本语言&#xff0c;使网页可交互与Java语法类似&#xff0c;但是不需要变异&#xff0c;直接由浏览器解析1995年Brendan Eich发明&#xff0c;1997年成为ECMA标准&#xff08;ECMA制定了标准化的脚本程序设计语言ECMAScr…

Mysql5.7主从数据库同步失败(日记文件错误)解决记录

记录一次Mysql主从数据库同步失败(日记文件错误)解决记录 查看同步状态&#xff1a; 具体错误&#xff1a; 检查mysql数据库日记 2021-06-10T03:45:43.522398Z 1 [ERROR] Error reading packet from server for channel : event read from binlog did not pass crc check; the…

NFC与ZigBee技术在智慧农业物联网监测系统中的应用

近年来&#xff0c;我国农业物联网技术飞速发展&#xff0c;基于物联网技术的智能农业监测系统有望得到较大规模的推广应用。但传统的物联网农业监测系统其网络结构层次单一&#xff0c;多采用基于有线或无线结构的节点-上位机数据采集模式&#xff0c;节点数据访问模式缺乏灵活…

音频、视频插座

音频、视频插座 常用电子元器件类型 DC电源插座 文章目录 音频、视频插座前言一、音频、视频插座二、DC电源插座1. 镀铜锡DC插座2. 镀镍DC插座总结前言 音频和视频插座在设计上具有特定的接口类型和标准,以确保兼容性和信号传输的质量。在选择插座时,需要根据设备的接口类…

云计算:OpenStack 配置云主机实例的存储挂载并实现外网互通

目录 一、实验 1. 环境 2.配置存储挂载 3.云主机实例连接外部网络&#xff08;SNAT&#xff09; 4.外部网络连接云主机实例&#xff08;DNAT&#xff09; 二、问题 1.云主机 ping 不通外部网络 2.nova list 查看云主机列表报错 3.nova list 与 virsh list --all有何区…

【音视频 ffmpeg 学习】 RTMP推流 mp4文件

1.RTMP(实时消息传输协议)是Adobe 公司开发的一个基于TCP的应用层协议。 2.RTMP协议中基本的数据单元称为消息&#xff08;Message&#xff09;。 3.当RTMP协议在互联网中传输数据的时候&#xff0c;消息会被拆分成更小的单元&#xff0c;称为消息块&#xff08;Chunk&#xff…

docker-compose Install TeamCity

前言 TeamCity 是一个通用的 CI/CD 软件平台,可实现灵活的工作流程、协作和开发实践。允许在您的 DevOps 流程中成功实现持续集成、持续交付和持续部署。 系统支持 docker download TeamCity TeamCity 文档参考项目离线包百度网盘获取

Spring Boot学习随笔- 集成MyBatis-Plus,第一个MP程序(环境搭建、@TableName、@TableId、@TableField示例)

学习视频&#xff1a;【编程不良人】Mybatis-Plus整合SpringBoot实战教程,提高的你开发效率,后端人员必备! 引言 MyBatis-Plus是一个基于MyBatis的增强工具&#xff0c;旨在简化开发&#xff0c;提高效率。它扩展了MyBatis的功能&#xff0c;提供了许多实用的特性&#xff0c;…

Qt/C++音视频开发62-电子放大/按下选择区域放大显示/任意选取区域放大

一、前言 电子放大这个功能思考了很久&#xff0c;也是一直拖到近期才静下心来完整这个小功能&#xff0c;这个功能的前提&#xff0c;主要得益于之前把滤镜打通了&#xff0c;玩出花样来了&#xff0c;只要传入对应的滤镜字符串&#xff0c;就可以实现各种各样的效果&#xf…

学习笔记:R语言基础

文章目录 一、R语言简介二、选择R的原因三、R基本数据对象&#xff08;一&#xff09;向量&#xff08;二&#xff09;矩阵&#xff08;三&#xff09;数组&#xff08;四&#xff09;因子&#xff08;五&#xff09;列表&#xff08;六&#xff09;数据框&#xff08;七&#…

LLM之RAG实战(十一)| 使用Mistral-7B和Langchain搭建基于PDF文件的聊天机器人

在本文中&#xff0c;使用LangChain、HuggingFaceEmbeddings和HuggingFace的Mistral-7B LLM创建一个简单的Python程序&#xff0c;可以从任何pdf文件中回答问题。 一、LangChain简介 LangChain是一个在语言模型之上开发上下文感知应用程序的框架。LangChain使用带prompt和few-…

数据结构入门到入土——ArrayList与顺序表

目录 一&#xff0c;线性表 二&#xff0c;顺序表 1.接口实现 三&#xff0c;ArrayList简介 四&#xff0c;ArrayList使用 1.ArrayList的构造 2.ArrayList常见操作 3.ArrayList的遍历 4.ArrayList的扩容机制 五&#xff0c;ArrayLisit的具体使用 杨辉三角 一&#x…

SONiC和ONL所依赖的Debian版本说明

Debian 的最新几个版本 下一代 Debian 正式发行版的代号为 trixie — 测试&#xff08;testing&#xff09;版 Debian 12 (bookworm) — 当前的稳定&#xff08;stable&#xff09;版 Debian 11 (bullseye) — 当前的旧的稳定&#xff08;oldstable&#xff09;版 Debian 10&a…

CentOS7 系统安装

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

jwt 介绍

目录 1&#xff0c;jwt 的出现问题 2&#xff0c;jwt 介绍3&#xff0c;jwt 令牌的组成3.1&#xff0c;header3.2&#xff0c;payload3.3&#xff0c;signature 4&#xff0c;验证5&#xff0c;总结 身份验证相关内容&#xff1a; 浏览器 cookie 的原理&#xff08;详&#xff…

计算机视觉技术-锚框

目标检测算法通常会在输入图像中采样大量的区域&#xff0c;然后判断这些区域中是否包含我们感兴趣的目标&#xff0c;并调整区域边界从而更准确地预测目标的真实边界框&#xff08;ground-truth bounding box&#xff09;。 不同的模型使用的区域采样方法可能不同。 这里我们介…

蓝牙物联网移动硬件数据传输系统解决方案

随着传感器技术、网络技术和数据传输技术的不断发展&#xff0c;人们对智能设备的需求日渐增强,利用传感器技术可以对周围环境进行准确和全面的感知&#xff0c;获取到实时信息&#xff0c;从而在网络中进行传输和共享&#xff0c;再通过服务器对各种数据进行保存、分析和挖掘等…

Transformer(seq2seq、self-attention)学习笔记

在self-attention 基础上记录一篇Transformer学习笔记 Transformer的网络结构EncoderDecoder 模型训练与评估 Transformer的网络结构 Transformer是一种seq2seq 模型。输入一个序列&#xff0c;经过encoder、decoder输出结果也是一个序列&#xff0c;输出序列的长度由模型决定…

乡村北斗预警预报应急通信调度方案

根据《中共中央国务院关于切实加强农业基础建设进一步促进农业发展农民增收的若干意见》&#xff08;中发[2008]1号&#xff09;等文件要求&#xff0c;要健全农业气象服务体系和农村气象灾害防御体系&#xff0c;充分发挥气象服务“三农”的重要作用。 随着中国北斗导航卫星系…