SpringBoot基于Zookeeper实现分布式锁

news2024/12/23 14:51:36

在这里插入图片描述

文章目录

  • 问题背景
  • 前言
  • 实现
    • 搭建Zookeeper容器
    • 引入依赖
    • ZK客户端的配置类
    • ZK客户端的工厂类
    • 注入bean
    • 构建测试类

问题背景

研究分布式锁,基于ZK实现,需要整合到SpringBoot使用

前言

  1. 参考自SpringBoot集成Curator实现Zookeeper基本操作,Zookeeper入门
  2. 本篇的代码笔者有自己运行过,需要注意组件的版本号是否兼容,否则会有比较多的坑

实现

搭建Zookeeper容器

采用Docker compose快速搭建ZK容器,很快,几分钟就好了,而且是集群方式搭建。详情见笔者的Docker搭建zookeeper

引入依赖

需要注意的点:Curator 2.x.x-兼容两个zk 3.4.xzk 3.5.xCurator 3.x.x-兼容兼容zk 3.5,根据搭建的zk的版本使用对应的curator依赖。引入的zk依赖,如果项目中有使用logback日志 ,需要排除zk中的log4j12依赖,详情见下面笔者给出的依赖:

<dependencies>
  <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-client</artifactId>
      <version>2.12.0</version>
  </dependency>

  <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-framework</artifactId>
      <version>2.12.0</version>
  </dependency>

  <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-recipes</artifactId>
      <version>2.12.0</version>
  </dependency>

  <dependency>
      <groupId>org.apache.zookeeper</groupId>
      <artifactId>zookeeper</artifactId>
      <version>3.5.7</version>
      <exclusions>
          <exclusion>
              <artifactId>slf4j-log4j12</artifactId>
              <groupId>org.slf4j</groupId>
          </exclusion>
          <exclusion>
              <artifactId>slf4j-api</artifactId>
              <groupId>org.slf4j</groupId>
          </exclusion>
      </exclusions>
  </dependency>

ZK客户端的配置类

配置ZK的参数,使用@ConfigurationProperties可以令配置热更新,比如搭配Apollo、Nacos,如果使用@Valid则无法热更新,必须重启项目才能生效

@Component
@ConfigurationProperties(prefix = "curator")
@Data
public class ZKClientProps {

    private String connectString;
    private int retryCount;
    private int elapsedTimeMs;
    private int sessionTimeoutMs;
    private int connectionTimeoutMs;
}

对应yml如下:

#curator配置
curator:
  connectString: 192.168.163.128:2181,192.168.163.128:2182,192.168.163.128:2183 # zookeeper 地址
  retryCount: 1 # 重试次数
  elapsedTimeMs: 2000 # 重试间隔时间
  sessionTimeoutMs: 60000 # session超时时间
  connectionTimeoutMs: 10000 # 连接超时时间

ZK客户端的工厂类

定制ZK客户端:

@Component
public class ZKClientFactory {

    @Resource
    private ZKClientProps zkClientProps;
    public CuratorFramework createSimple() {
        //重试策略:第一次重试等待1S,第二次重试等待2S,第三次重试等待4s

        //第一个参数:等待时间的基础单位,单位为毫秒
        //第二个参数:最大重试次数
        ExponentialBackoffRetry retry = new ExponentialBackoffRetry(zkClientProps.getElapsedTimeMs(), zkClientProps.getRetryCount());

        //获取CuratorFramework示例的最简单方式
        //第一个参数:zk的连接地址
        //第二个参数:重试策略
        return CuratorFrameworkFactory.newClient(zkClientProps.getConnectString(), retry);
    }


    public static CuratorFramework createWithOptions(String connectionString, RetryPolicy retryPolicy,
                                                     int connectionTimeoutMs, int sessionTimeoutMs) {
        return CuratorFrameworkFactory.builder().connectString(connectionString)
                .retryPolicy(retryPolicy).connectionTimeoutMs(connectionTimeoutMs).sessionTimeoutMs(sessionTimeoutMs).build();
    }
}

注入bean

创建ZK的客户端,详情如下:

@Component
@Slf4j
public class ZKClient {

    @Resource
    private ZKClientFactory zkClientFactory;
    public static final ZKClient INSTANCE = new ZKClient();

    private ZKClient() {
    }

    public CuratorFramework getClient() {
        return zkClientFactory.createSimple();
    }

    public boolean isNodeExist(String path) {
        CuratorFramework client = getClient();
        try {
            client.start();
            Stat stat = client.checkExists().forPath(path);
            return stat != null;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            CloseableUtils.closeQuietly(client);
        }
        return false;
    }

    public void createNode(String path, byte[] bytes) {
        CuratorFramework client = getClient();
        try {
            // 必须start,否则报错
            client.start();
            client.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path, bytes);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            CloseableUtils.closeQuietly(client);
        }
    }

    public void deleteNode(String path) {
        CuratorFramework client = getClient();
        try {
            client.start();
            client.delete().forPath(path);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            CloseableUtils.closeQuietly(client);
        }
    }

    public List<String> getChildren(String path) {
        List<String> result = new LinkedList<>();
        CuratorFramework client = getClient();
        try {
            client.start();
            result = client.getChildren().forPath(path);
        } catch (Exception e) {
            log.error("ZKClient getChildren error.");
        }
        return result;
    }

}

构建测试类

测试基类,设置激活环境

@Slf4j
@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@SpringBootTest(classes = GmallZookeeperApplication.class)
@ContextConfiguration
public class BaseTest {

}

创建节点、删除节点、获取节点信息、分布式锁的方法如下:@ActiveProfiles("company")是激活笔者一个application-company.yml文件

application.yml如下:

server:
  port: 8022

spring:
  profiles:
    active: home

application-compay.yml如下:

#curator配置
curator:
  connectString: 192.168.163.128:2181,192.168.163.128:2182,192.168.163.128:2183 # zookeeper 地址
  retryCount: 1 # 重试次数
  elapsedTimeMs: 2000 # 重试间隔时间
  sessionTimeoutMs: 60000 # session超时时间
  connectionTimeoutMs: 10000 # 连接超时时间

创建节点、删除节点、获取节点信息、分布式锁的方法如下:

@Slf4j
@ActiveProfiles("company")
public class ZKClientTest extends BaseTest{

    @Resource
    private ZKClient zkClient;
    public static final int THREAD_NUM = 10;

    @Test
    public void distributedLock() throws InterruptedException, BrokenBarrierException {
        String lockPath = "/test/distributed2/lock";
        CuratorFramework client = zkClient.getClient();
        client.start();
        InterProcessMutex lock = new InterProcessMutex(client, lockPath);

        // 阻塞主线程,等待全部子线程执行完
        CyclicBarrier cyclicBarrier = new CyclicBarrier(THREAD_NUM);

        for (int i = 0; i < THREAD_NUM; i++) {
            new Thread(() -> {
                log.info("{}->尝试竞争锁", Thread.currentThread().getName());
                try {
                    lock.acquire(); // 阻塞竞争锁

                    log.info("{}->成功获得锁", Thread.currentThread().getName());
                    Thread.sleep(2000);

                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        lock.release(); //释放锁
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

            }, "Thread-" + i).start();
        }

        // 目的是为了等子线程抢完锁再结束子线程,否则无法看到日志效果
        cyclicBarrier.await();
        log.info("全部子线程已执行完毕");
    }

    @Test
    public void createNode() {
        // 创建一个ZNode节点
        String data = "hello";
        byte[] payload = data.getBytes(StandardCharsets.UTF_8);
        String zkPath = "/test/CRUD/node-1";

        zkClient.createNode(zkPath, payload);
        log.info("createNode succeeded!");

    }

    @Test
    public void getChildren() {
        String zkPath = "/test/CRUD";

        List<String> children = zkClient.getChildren(zkPath);
        printList(children);
    }

    @Test
    public void deleteNode() {
        String parentPath = "/test";

        log.info("======================Before delete===================");
        List<String> before = zkClient.getChildren(parentPath);
        printList(before);

        String zkPath = "/test/CRUD/node-1";
        zkClient.deleteNode(zkPath);
        log.info("delete node secceeded!");

        log.info("======================After delete===================");
        List<String> after = zkClient.getChildren(parentPath);
        printList(after);
    }

    private void printList(List<String> data) {
        if (!CollectionUtils.isEmpty(data)) {
            for (String datum : data) {
                log.info("datum:{}", data);
            }
        }
    }
}

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

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

相关文章

d3dx9_37.dll如何修复,d3dx9_37.dll丢失的4种解决方法分享

d3dx9_37.dll是DirectX中的一个动态链接库文件&#xff0c;它包含了一些用于游戏和图形应用程序的函数和资源。当你在运行一个需要使用DirectX的程序时&#xff0c;如果系统中缺少d3dx9_37.dll文件或该文件损坏&#xff0c;就会导致程序无法正常运行。 以下是解决d3dx9_37.dll问…

数字孪生如何实现物理世界和数字世界之间的交互?

在当今数字化时代&#xff0c;技术的飞速发展正在引领着各行各业的变革与创新。其中&#xff0c;数字孪生作为一项令人振奋的前沿技术&#xff0c;正在以惊人的方式实现着物理世界与数字世界的无缝交互。它不仅为企业带来了全新的商机&#xff0c;也为科学研究、生产制造等领域…

算法通关村第七关——递归和迭代实现二叉树前中后序遍历

1.递归 1.1 熟悉递归 所有的递归有两个基本特征&#xff1a; 执行时范围不断缩小&#xff0c;这样才能触底反弹。终止判断在调用递归的前面。 写递归的步骤&#xff1a; 从小到大递推。分情况讨论&#xff0c;明确结束条件。组合出完整方法。想验证就从大到小画图推演。 …

Android Ble蓝牙App(四)UI优化和描述符

Ble蓝牙App&#xff08;四&#xff09;UI优化和描述符 前言正文一、UI优化① 主页面提示优化② UUID显示优化③ 设备信息优化 二、描述① 概念② 描述适配器③ 显示描述符 三、源码 前言 上一篇中了解了特性和属性&#xff0c;同时显示设备蓝牙服务下的特性和属性&#xff0c;本…

约数个数(质因子分解)

思路&#xff1a; &#xff08;1&#xff09;由数论基本定理&#xff0c;任何一个正整数x都能写作&#xff0c;其中p1,p2..pk为x的质因子。 &#xff08;2&#xff09;由此可以推断&#xff0c;要求一个数约数的个数&#xff0c;注意到约数就是p1,p2...pk的一种组合&#xff…

可重入锁,不可重入锁,死锁的多种情况,以及产生的原因,如何解决,synchronized采用的锁策略(渣女圣经)自适应的底层,锁清除,锁粗化,CAS的部分应用

一、&#x1f49b; 锁策略——接上一篇 6.分为可重入锁&#xff0c;不可重入锁 如果一个线程&#xff0c;针对一把锁&#xff0c;连续加锁两次&#xff0c;会出现死锁&#xff0c;就是不可重入锁&#xff0c;不会出现死锁&#xff0c;就是可重入锁。 如果一个线程&#xff0c;针…

yum包管理器

文章目录 是什么&#xff1f;联系本地源⽹络源 为什么要用yum&#xff1f;联系 是什么&#xff1f; 我们将yum称为包管理器&#xff0c;主要用来解决&#xff1a;下载、依赖关系、安装、卸载四种问题。 在Linux系统中有一个yum软件服务器&#xff0c;上面有Linux需要的各种软件…

QT:自定义控件(Connect使用,子控件连接)

自定义控件封装&#xff1a; 1.添加新文件&#xff08;设计师界面类&#xff09;&#xff0c;创建子页面 &#xff0c;放自己想要的控件 2.在主页面中使用子控件 :新建一个widget-![在这里插入图片描述](https://img-blog.csdnimg.cn/95ed8015343e4c56a3914853950eff4c.png#pi…

中国艺术孙溟㠭篆刻作品《得大自在》

关汉卿《四块玉闲适》&#xff1a;“适意行&#xff0c;安心坐。渴时饮&#xff0c;饥时餐&#xff0c;醉时歌。困来时就向莎茵卧。日月长&#xff0c;天地阔&#xff0c;闲快活。” 整理/释门

uniapp+uview封装小程序请求

提要&#xff1a; uniapp项目引入uview库 此步骤不再阐述 1.创建环境文件 env.js&#xff1a; let BASE_URL;if (process.env.NODE_ENV development) {// 开发环境BASE_URL 请求地址; } else {// 生产环境BASE_URL 请求地址; }export default BASE_URL; 2.创建请求文件 该…

2023牛客暑期多校训练营9-Non-Puzzle: Segment Pair

2023牛客暑期多校训练营9-Non-Puzzle: Segment Pair https://ac.nowcoder.com/acm/contest/57363/I 文章目录 2023牛客暑期多校训练营9-Non-Puzzle: Segment Pair题目大意解题思路代码 题目大意 解题思路 对于每一对 [ l i , r i ] [l_i,r_i] [li​,ri​]和 [ l i ′ , r i …

海信聚好看将携新品DBdoctor,亮相中国数据库技术大会(DTCC2023)

海信聚好看将携新品DBdoctor&#xff0c;亮相中国数据库技术大会 8月16日—18日&#xff0c;第14届中国数据库技术大会&#xff08;DTCC-2023&#xff09;将在北京国际会议中心隆重召开。作为国内数据库领域规模最大的技术交流盛会&#xff0c;吸引了众多业内知名企业和数百名…

2023 8 -14链表OJ

&#x1f495;人面只今何处去&#xff0c;桃花依旧笑春风&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;详解链表OJ题 题目一&#xff1a;环形链表&#xff08;判断链表是否带环&#xff09; 题目描述&#xff1a; 画图分析&#xff1a; 代码实现&#x…

怎么把太大的视频压缩变小?这样压缩很轻松

很多网站或者平台对于上传的视频都是有大小限制的&#xff0c;当视频文件体积过大时&#xff0c;我们就需要进行压缩操作&#xff0c;下面就给大家分享几个实用的视频压缩方法&#xff0c;不仅压缩率高&#xff0c;还能保证视频清晰度哦~ 一、HandBrake 这是一款免费的视频处理…

小程序商品如何指定支付方式

不同的支付方式可以满足用户的不同需求&#xff0c;提供更加便捷和灵活的购物体验。例如有些商品需要在线支付&#xff0c;有些商品需要积分支付&#xff0c;有些商品需要货到付款等等。下面就介绍一些关于小程序产品怎么指定支付方式&#xff0c;并且列举了一些常见的支付方式…

RocketMQ 消息消费 轮询机制 PullRequestHoldService

1. 概述 先来看看 RocketMQ 消费过程中的轮询机制是啥。首先需要补充一点消费相关的前置知识。 1.1 消息消费方式 RocketMQ 支持多种消费方式&#xff0c;包括 Push 模式和 Pull 模式 Pull 模式&#xff1a;用户自己进行消息的拉取和消费进度的更新Push 模式&#xff1a;Broker…

江南大学计算机考研分析

24计算机考研|上岸指南 江南大学 江南大学计算机考研招生学院是人工智能与计算机学院。目前均已出拟录取名单。 江南大学人工智能与计算机学院成立于2020年3月&#xff0c;办学历史可追溯到1994年设立的计算机应用专业。学院秉持江南大学“彰显轻工特色&#xff0c;服务国计民…

关于MYSQL日期相减问题

错误写法&#xff1a; SELECT DATE_FORMAT(STR_TO_DATE(20230701,%Y%m%d) -60,%Y%m%d); 但是这种格式有个问题&#xff0c;则会输出空。 正确写法&#xff1a; SELECT DATE_FORMAT(DATE_SUB(20230701,INTERVAL 60 DAY),%Y%m%d);

cad斜线怎么标注尺寸?

好多朋友都在问CAD斜线怎么标注尺寸&#xff0c;CAD绘图中有很多图都是不规则的&#xff0c;你知道CAD中如何对斜线进行标注长度吗&#xff1f;这次将为大家带来cad斜线标注尺寸的方法&#xff0c;希望大家在cad中可以灵活使用&#xff01; 1&#xff1a;首先&#xff0c;我们…

可独立创建应用的SaaS多租户低代码平台之租户的应用管理说明

在IT系统中&#xff0c;“租户”&#xff08;tenant&#xff09;通常用于指代一种多租户架构&#xff08;multi-tenancy&#xff09;&#xff0c;它是一种软件架构模式&#xff0c;允许多个用户或组织共享相同的应用程序或系统实例&#xff0c;但彼此之间的数据和配置被隔离开来…