3、基于Zookeeper实现分布式锁

目录

      • 3.1、Zookeeper安装和相关概念
        • 3.1.1 安装启动
        • 3.1.2 相关概念
        • 3.1.3 Java客户端
      • 3.2 Zookeeper实现分布式锁的思路分析
      • 3.3 ZooKeeper分布式锁的基本实现

3.1、Zookeeper安装和相关概念

3.1.1 安装启动

# 解压到/mysoft文件夹下
tar -zxvf zookeeper-3.7.0-bin.tar.gz
# 重命名
mv apache-zookeeper-3.7.0-bin/ zookeeper
# 打开zookeeper根目录
cd /mysoft/zookeeper
# 创建一个数据目录,备用
mkdir data
# 打开zk的配置目录
cd /mysoft/zookeeper/conf
# copy配置文件,zk启动时会加载zoo.cfg文件
cp zoo_sample.cfg zoo.cfg
# 编辑配置文件
vim zoo.cfg
# 修改dataDir参数为之前创建的数据目录:/mysoft/zookeeper/data
# 切换到bin目录
cd /mysoft/zookeeper/bin
# 启动 
./zkServer.sh start
./zkServer.sh status # 查看启动状态
./zkServer.sh stop # 停止
./zkServer.sh restart # 重启
./zkCli.sh # 查看zk客户端

如下,说明启动成功:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qh8aEaiD-1690013928249)(E:\学习笔记\分布式锁\分布式锁全家桶-尚硅谷.assets\image-20230722002149142.png)]

3.1.2 相关概念

Zookeeper提供一个多层级的节点命名空间(节点称为znode),每个节点都用一个以斜杠(/)分隔的路径表示,而且每个节点都有父节点(根节点除外),非常类似于文件系统。并且每个节点都是唯一的。

1、znode节点有四种类型:

  • PERSISTENT:永久节点。客户端与zookeeper断开连接后,该节点依旧存在
  • EPHEMERAL:临时节点。客户端与zookeeper断开连接后,该节点被删除
  • PERSISTENT_SEQUENTIAL:永久节点、序列化。客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号
  • EPHEMERAL_SEQUENTIAL:临时节点、序列化。客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号
create /aa test  # 创建持久化节点
create -e /cc test  # 创建临时节点
create -s /bb test  # 创建持久序列化节点
create -e -s /dd test  # 创建临时序列化节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HpbixRIh-1690013949744)(E:\学习笔记\分布式锁\分布式锁全家桶-尚硅谷.assets\image-20230722084817127.png)]

2、事件监听

在读取数据时,我们可以同时对节点设置事件监听,当节点数据或结构变化时,zookeeper会通知客户端。当前zookeeper针对节点的监听有如下四种事件:

  1. 节点创建:stat -w /xx

    当/xx节点创建时:NodeCreated

  2. 节点删除:stat -w /xx

    当/xx节点删除时:NodeDeleted

  3. 节点数据修改:get -w /xx

    当/xx节点数据发生变化时:NodeDataChanged

  4. 子节点变更:ls -w /xx

    当/xx节点的子节点创建或者删除时:NodeChildChanged

【注意】zookeeper监听都是一次性的

3.1.3 Java客户端

ZooKeeper的java客户端有:

  • 原生客户端
  • ZkClient
  • Curator框架(类似于redisson,有很多功能性封装)

Java命令演示:

public class ZkTest {
    public static void main(String[] args) throws InterruptedException {
        ZooKeeper zooKeeper = null;
        CountDownLatch countDownLatch = new CountDownLatch(1);
        try {
            zooKeeper = new ZooKeeper("192.168.239.11:2181", 30000, new Watcher() {

                @Override
                public void process(WatchedEvent event) {
                    Event.KeeperState state = event.getState();
                    if (Event.KeeperState.SyncConnected.equals(state)) {
                        System.out.println("获取zookeeper连接了.....");
                        countDownLatch.countDown();
                    } else if (Event.KeeperState.Closed.equals(state)) {
                        System.out.println("关闭连接......");
                    }
                }
            });
            countDownLatch.await();
            System.out.println("zookeeper一顿操作...");
            // 一、创建一个节点
            // 创建一个节点,1-节点路径 2-节点内容 3-节点的访问权限 4-节点类型
            // OPEN_ACL_UNSAFE:任何人可以操作该节点
            // CREATOR_ALL_ACL:创建者拥有所有访问权限
            // READ_ACL_UNSAFE: 任何人都可以读取该节点
            // 创建持久化节点
            // zooKeeper.create("/atguigu", "haha~~".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            // 创建临时节点
            // zooKeeper.create("/test", "haha~~".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
            // 创建永久序列化
            // zooKeeper.create("/atguigu/cc", "haha~~".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
            // 创建临时序列化节点
            // zooKeeper.create("/atguigu/dd", "haha~~".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            // zooKeeper.create("/atguigu/dd", "haha~~".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
            // zooKeeper.create("/atguigu/dd", "haha~~".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);


            // 二、判断节点是否存在
            Stat stat = zooKeeper.exists("/test", true);
            if (stat != null){
                System.out.println("当前节点存在!" + stat.getVersion());
            } else {
                System.out.println("当前节点不存在!");
            }

            // 三、获取一个节点的数据
            byte[] data = zooKeeper.getData("/aa/bb0000000000", false, null);
            System.out.println("节点/aa/bb0000000000下的数据为:" + new String(data));

            // 查询一个节点的所有子节点
            List<String> children = zooKeeper.getChildren("/aa", new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    System.out.println("的子节点发生变化");
                }
            });
            System.out.println("/aa节点下的子节点:" + children);

            // 更新
            // zooKeeper.setData("/atguigu", "wawa...".getBytes(), stat.getVersion());

            // 删除一个节点
            //zooKeeper.delete("/atguigu", -1);

            System.in.read();

        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (KeeperException e) {
            throw new RuntimeException(e);
        } finally {
            if (zooKeeper != null) {
                zooKeeper.close();
            }
        }
    }
}

3.2 Zookeeper实现分布式锁的思路分析

获取锁:create一个节点(因为一个节点只能创建一次,当一个线程创建完一个节点后,其他线程都不能创建该节点了)
删除锁:delete一个节点

参照redis分布式锁的特点:

  1. 互斥 排他
  2. 防死锁:
    1. 可自动释放锁(临时节点) :获得锁之后客户端所在机器宕机了,客户端没有主动删除子节点;如果创建的是永久的节点,那么这个锁永远不会释放,导致死锁;由于创建的是临时节点,客户端宕机后,过了一定时间zookeeper没有收到客户端的心跳包判断会话失效,将临时节点删除从而释放锁。
    2. 可重入锁:借助于ThreadLocal
  3. 防误删:宕机自动释放临时节点,不需要设置过期时间,也就不存在误删问题。
  4. 加锁/解锁要具备原子性
  5. 单点问题:使用Zookeeper可以有效的解决单点问题,ZK一般是集群部署的。
  6. 集群问题:zookeeper集群是强一致性的,只要集群中有半数以上的机器存活,就可以对外提供服务。

3.3 ZooKeeper分布式锁的基本实现

实现思路:

  1. 多个请求同时添加一个相同的临时节点,只有一个可以添加成功。添加成功的获取到锁
  2. 执行业务逻辑
  3. 完成业务流程后,删除节点释放锁。

基本实现

由于zookeeper获取链接是一个耗时过程,这里可以在项目启动时,初始化链接,并且只初始化一次。借助于spring特性,代码实现如下:

public class ZkClient {

    private ZooKeeper zooKeeper;

    @PostConstruct
    public void init() {
        // 项目启动时,创建zk连接
        CountDownLatch countDownLatch = new CountDownLatch(1);
        try {
            zooKeeper = new ZooKeeper("192.168.239.11:2181", 30000, new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    Event.KeeperState state = event.getState();
                    if (Event.KeeperState.SyncConnected.equals(state)) {
                        System.out.println("获取zookeeper连接了.....");
                        countDownLatch.countDown();
                    } else if (Event.KeeperState.Closed.equals(state)) {
                        System.out.println("关闭连接......");
                    }
                }
            });
        } catch (IOException e) {
           e.printStackTrace();
        }
    }

    @PreDestroy
    public void destory() {
        // 项目销毁前,释放zk连接
        if(zooKeeper!=null) {
            try {
                zooKeeper.close();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    // 获取分布式锁
    public ZkDistributedLock getLock(String name) {
        return new ZkDistributedLock(name,zooKeeper);
    }
}

zk分布式锁具体实现:

public class ZkDistributedLock {

    private static final String ROOT_PATH = "/distributed";

    private String path;

    private ZooKeeper zooKeeper;

    public ZkDistributedLock(ZooKeeper zooKeeper, String lockName){
        this.zooKeeper = zooKeeper;
        this.path = ROOT_PATH + "/" + lockName;
    }

    public void lock(){
        try {
            zooKeeper.create(path, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
        } catch (Exception e) {
            // 重试
            try {
                Thread.sleep(200);
                lock();
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    public void unlock(){
        try {
            this.zooKeeper.delete(path, 0);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }
}

改造StockService的checkAndLock方法:

@Autowired
private ZkClient client;

public void checkAndLock() {
    // 加锁,获取锁失败重试
    ZkDistributedLock lock = this.client.getZkDistributedLock("lock");
    lock.lock();

    // 先查询库存是否充足
    Stock stock = this.stockMapper.selectById(1L);
    // 再减库存
    if (stock != null && stock.getCount() > 0){
        stock.setCount(stock.getCount() - 1);
        this.stockMapper.updateById(stock);
    }

    // 释放锁
    lock.unlock();
}

测试

存在的问题

  • 性能一般(比mysql分布式锁略好)
  • 不可重入

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

剑指offer61.扑克牌中的顺子

我的想法非常简单&#xff0c;就是先给数组排序&#xff0c;然后统计里面有几个0&#xff0c;然后遍历数组&#xff0c;如果是0或者比后面一个数小1就直接进入下一次循环&#xff0c;如果比后面一个数小2&#xff0c;就用掉一个0&#xff0c;0的数量减1&#xff0c;如果比后面的…

MySQL基础(二)SQL语句

目录 前言 一、SQL语句类型 二、数据库操作 查看 创建 进入 删除 更改 三、数据表操作 &#xff08;一&#xff09;、数据类型 1.数值类型 2.时间\日期类型 3.字符串类型 4.二进制类型 &#xff08;二&#xff09;、查看 1.查看表内的数据 2.查看表格的属性 &…

【Java基础教程】(四十六)IO篇 · 下:System类对IO的支持:错误输出、信息输出、系统输入,字符缓冲流、扫描流和对象序列化流~

Java基础教程之IO操作 下 &#x1f539;本节学习目标1️⃣ System类对 IO 的支持1.1 错误输出&#xff1a;System.err1.2 信息输出&#xff1a;System.out1.3 系统输入&#xff1a;System. in 2️⃣ 字符缓冲流&#xff1a;BufferedReader3️⃣ 扫描流&#xff1a;Scanner4️⃣…

Python - Opencv应用实例之树叶自动分割、标签及统计分析系统

Python - Opencv应用实例之树叶自动分割、标签及统计分析系统 本文通过Python+opencv 实现这样的需求:输出位置和角度(x, y, r),并标记出轮廓基于传统图像处理算法实现,算法原理:输入图像 -> 灰度化 -> 二值化 -> 形态学处理 -> 轮廓提取 -> 树叶中心定位 -…

matlab二维图形的绘制(1)

概述 matlab数学计算中有时候需要将函数通过图形更加直观的展示给用户的时候就需要使用到二维绘图。绘图常常采用的绘图命令便是 plot &#xff0c;再添加一些辅助命令使得图形更加的形象直观。 二维绘图中也包括针状图&#xff0c;阶梯图&#xff0c;散点图&#xff0c;条形图…

解决行业反复“造轮子”现象,全新地平线RDK系列机器人开发者套件上线

7月25日&#xff0c;“地平线2023机器人开发者创享日”在深圳举办&#xff0c;地平线RDK系列机器人开发者套件正式上线&#xff0c;机器人操作系统TogetheROS.Bot™2.0版发布&#xff0c;应用中心NodeHub首发亮相&#xff0c;地平线开发者社区改版上线。 地平线2023机器人开发者…

文件读写流函数

文件 文件读写 链接 std::ifstream---std::ofstream 头文件--#include <fstream> 执行都是类&#xff0c;用这些类操作文件都要建立对象流。 1&#xff0c;建立对象流 流对象的建立有两种方式&#xff1a; &#xff08;1&#xff09;使用fstream类可以不指定文件和…

python+unittest+requests+HTMLRunner搭建接口测试框架,执行用例请求多个不同请求方式的接口

问题描述&#xff1a; 搭建接口测试框架&#xff0c;执行用例请求多个不同请求方式的接口 实现步骤&#xff1a; ① 创建配置文件config.ini&#xff0c;写入部分公用参数&#xff0c;如接口的基本url、测试报告文件路径、测试数据文件路径等配置项 1 [DATABASE] 2 data_add…

深入解析 Kubernetes 架构:掌握主节点、工作节点和容器运行时

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

级联下拉单选框的实现

需求背景 选择某个城市&#xff0c;所在区县与所选城市要一一对应。 开发思路 因为单选框使用的是公共组件&#xff0c;获取的val是“深圳市”&#xff0c;而不是索引&#xff0c;那么可以 先判断出选择的城市的索引值&#xff1b;由此所在区县的下拉框的数据直接由所获的索…

android studio JNI开发

一、JNI的作用&#xff1a; 1.使Java与本地其他类型语言&#xff08;C、C&#xff09;交互&#xff1b; 2.在Java代码调用C、C等语言的代码 或者 C、C调用Java代码。 由于JAVA具有跨平台的特点&#xff0c;所以JAVA与本地代码的交互能力弱&#xff0c;采用JNI特性可以增强JA…

HMM与CRF模型的使用过程有哪些差异?

在NLP领域, HMM用来解决文本序列标注问题. 如分词, 词性标注, 命名实体识别都可以看作是序列标注问题。同HMM一样, CRF一般也以文本序列数据为输入, 以该序列对应的隐含序列为输出。 HMM模型 HMM模型表示为: lambda HMM(A, B, pi), 其中A, B, pi都是模型的参数, 分别称作: 转…

超详细-Vivado配置Sublime+Sublime实现Verilog语法实时检查

目录 一、前言 二、准备工作 三、Vivado配置Sublime 3.1 Vivado配置Sublime 3.2 环境变量添加 3.3 环境变量验证 3.4 Vivado设置 3.5 配置验证 3.6 解决Vivado配置失败问题 四、Sublime配置 4.1 Sublime安装Package Control 4.2 Sublime安装Verilog插件 4.3 安装语…

Vue中TodoList案例_编辑

nextTick: MyItem.vue 加一个编辑按钮&#xff0c;input框&#xff1a;blur失去焦点时触发事件handleBlur&#xff0c;ref获取真实dom&#xff1a; <inputtype"text"v-show"todo.isEdit":value"todo.title"blur"handleBlur(todo,$even…

外贸找客户工具之邮件群发:MaxBulk Bulk Mailer Pro 9.5

MaxBulk Bulk Mailer Pro 是一款快速的批量邮件软件&#xff0c;旨在帮助在一次操作中向大量电子邮件 ID 发送批量电子邮件。直接将电子邮件发送到收件箱而不是垃圾邮件。该工具的目的是使批量电子邮件处理过程快速而精确&#xff0c;并且它配备了很多高级功能来实现此目的。用…

VSCode同时编译多个C文件

一.环境说明 1.系统&#xff1a;Ubuntu 22.04.2 LTS 2.Visual Studio Code: 1.80.1 二.问题描述 今天使用VSCode编译《Programming Abstractions In C》书中的gymjudge.c代码时遇到错误&#xff0c;错误提示为&#xff1a; (base) codistspc:~/projects/Programming-Abstracti…

Redis【实践篇】之RedisTemplate基本操作

Redis 从入门到精通【应用篇】之RedisTemplate详解 文章目录 Redis 从入门到精通【应用篇】之RedisTemplate详解0. 前言1. RedisTemplate 方法1. 设置RedisTemplate的序列化方式2. RedisTemplate的基本操作 2. 源码浅析2.1. 构造方法2.2. 序列化方式2.3. RedisTemplate的操作方…

PGembedding 代码分析

pgembedding 存储结构 pg embedding 数据是存在共享内存中的&#xff0c;pg down 之后索引数据就没了&#xff0c;但索引对象本身还在&#xff0c;第一次访问时会重新创建。 数据以 plain 的形式存储&#xff0c;其中每个点是这样的结构&#xff1a; idx_size&#xff1a; 表…

继承-菱形继承

继承 继承是类设计层次的复用 继承方式与访问限定符 限定了啥&#xff1f; 1.根据表中我们可以看到 基类的私有成员在子类不可见&#xff0c;但还是被继承了下来 2.根据继承方式和成员在基类的访问限定符小的那个来决定了子类访问基类成员的访问方式 例如如果是public继承&a…

Linux工具——vim

安装vim yum -y install vim 如果安装失败&#xff0c;提示Could not resolve host:mirrorlist.centos.org: Unkown error的问题&#xff0c;需要替换yum源&#xff0c;可以参考这个文章 配置vim root的vim配置文件在 /etc/vimrc 普通用户的vim配置文件在用户对应家目录下&a…
最新文章