Zookeeper(三)—分布式锁实现

2023-11-20

一、独占锁原理

独占锁是利用zk同一目录下不能创建多个相同名称的节点这个特性,来实现分布式锁的功能。
在这里插入图片描述
竞争锁的分布式系统,都在zk根目录下创建一个名为lock的节点。创建节点成功的系统,说明抢到了这把锁,没有创建成功的系统,说明这个节点已经被其他系统创建了,没有抢到锁,那么就监听这个节点的删除事件,来等待锁的释放。
当抢到锁的系统执行完业务逻辑后,删除这个lock节点。zk会向监听这个lock节点的所有客户端发送通知,告知lock节点被删除了。接到通知的各系统再次去创建lock节点。创建成功的,证明抢到了这把锁。然后循环上面的过程,以此实现分布式锁的功能。

弊端:
独占锁的弊端就是,如果抢锁的分布式系统很多的话,zk向各系统发送通知时,是走网络通信的,很多的客户端需要通知,就是大量的网络传输,很影响性能。如果分布式子系统少的话,这种方式可以考虑。

二、非独占锁原理

针对上述独占锁的设计缺陷,又提出了非独占锁的实现思路。非独占锁利用zk的有序节点的特性,对分布式系统进行排序,然后按照排序,依次给分布式系统抢到锁,执行业务。
在这里插入图片描述
争抢锁的分布式系统在lock节点下创建临时有序节点。各系统创建了节点后,可以获取到自己创建的节点编号。然后获取lock节点下的所有子目录,看自己创建的编号是否为最小。如果是最小,就争抢到了锁,执行锁住的业务逻辑。如果不是最小,就监听前一个编号的节点。
当抢到锁的系统执行完业务代码后,删除这个节点,zk通知监听该节点的客户端,去执行锁住的代码。依次类推,完成分布式锁功能。

可以看到,这种实现方式,zk每次只通知一个客户端去争抢锁,解决了独占锁设计中的缺陷问题。

三、代码实现

zk实现分布式锁的代码在zk的java客户端中已经实现好了。我们在使用时直接调用客户端提供的方法实现分布式锁即可。这里为了锻炼一下自己的代码水平,手动用代码实现一下上述的独占锁和非独占锁的原理。
注:以下代码实现使用ZkClient客户端连接操作zk服务。

第一步:
通过读框架源码可知,优秀的框架,都是从定义接口规范开始的,这里我们也要建立这种思想。先定义规范,再考虑实现。

首先,定义接口规范Lock接口:

public interface Lock {
    public void lock();
    public void unlock();
}

第二步:
然后,实现上述接口。先捋清楚上述接口方法的逻辑代码。

public void lock(){
//尝试获取锁
boolean getLock=tryLock();
//如果获取到锁,执行业务代码
if(getLock){//抢到锁,执行业务代码
}else{//没抢到锁,等待锁释放,重新抢锁
waitLock();
lock();
}
}

上述伪代码是利用递归的方式,一直抢锁,直到抢锁成功。为了实现lock加锁抢锁功能,我们需要实现获取锁和锁等待的逻辑。而通过独占锁和非独占锁的原理可知,两种方式获取锁和抢锁的实现是不一样的,因此,我们先封装出一个抽象类,来实现lock方法,而对于lock内部的获取锁和锁等待的方法,我们用模板设计模式,让不同的子类去实现。之所以手写代码实现zk的分布式锁,就是为了体会这种编码思想和规范。

public abstract class ZkAbstractLock implements Lock {

    private static String connectStr = "ip:port";

    public static String path = "/lock";

    protected ZkClient client = new ZkClient(connectStr);

    /**
        lock方式是要去获取锁的方法
        如果成功,那么代码往下走,执行创建订单的业务逻辑

        如果失败,lock需要等待
        1、等待到了前面那个获取锁的客户端释放锁以后
        2、再去重新获取锁
    */
    @Override
    public void lock() {
        //1、尝试去获取锁
        if(tryLock()) {
            System.out.println(Thread.currentThread().getName() + "--->获取锁成功!");
        } else {
            //在这里等待
            waitforlock();
            lock();
        }
    }

    //钩子方法
    protected abstract boolean tryLock();
    //钩子方法
    protected abstract void waitforlock();
    //创建的临时节点,关闭session,节点自动删除
    @Override
    public void unlock() {
        client.close();
    }
}

第三步:
实现独占锁和非独占锁的获取锁和锁等待的代码实现。
独占锁:

public class ZkLockImpl extends ZkAbstractLock {

    private CountDownLatch cdl = null;

    //尝试获取锁
    @Override
    protected boolean tryLock() {
        try {
            client.createEphemeral(path);
            return true;
        } catch (ZkException e) {
            return false;
        }
    }

    //等待获取锁
    //等前面那个获取锁成功的客户端释放锁

    //没有获取到锁的客户端都会走到这里
    //1、没有获取到锁的要注册对/lock节点的watcher
    //2、这个方法需要等待
    @Override
    protected void waitforlock() {
        IZkDataListener iZkDataListener = new IZkDataListener() {
            @Override
            public void handleDataChange(String dataPath, Object data) throws Exception {

            }
            //一旦/lock节点被删除以后,就会触发这个方法
            @Override
            public void handleDataDeleted(String dataPath) throws Exception {
                //让等待的代码不再等待了
                if(cdl != null) {
                    cdl.countDown();
                }
            }
        };
        //注册watcher
        client.subscribeDataChanges(path, iZkDataListener);

        if (client.exists(path)) {
            cdl = new CountDownLatch(1);
            try {
                cdl.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //取消该客户端的订阅关系
        client.unsubscribeDataChanges(path, iZkDataListener);
    }
}

非独占锁:

public class ZkImproveLockImpl extends ZkAbstractLock {
    //记录当前客户端创建的临时节点
    private String currentPath;

    //记录上一个节点
    private String beforePath;

    private CountDownLatch cdl;

    public ZkImproveLockImpl() {
        if(!client.exists(path)) {
            client.createPersistent(path,"");
        }
    }

    @Override
    protected boolean tryLock() {
        if (currentPath == null || currentPath.length() <= 0) {
            // /lock/0000000001
            currentPath = client.createEphemeralSequential(path + "/", "");
        }

        //拿到/lock下面的所有儿子节点
        List<String> children = client.getChildren(path);
        Collections.sort(children);
        //children.get(0) 就是最小的那个节点
        if (currentPath.equals(path + "/" + children.get(0))) {
            return true;
        } else {
            //如果不是第一个,那么就必须找出当前节点的上一个节点
            //找到当前节点在所有子节点的索引
            int i = Collections.binarySearch(children, currentPath.substring(6));
            beforePath = path + "/" + children.get(i - 1);
        }
        return false;
    }

    @Override
    protected void waitforlock() {
        IZkDataListener iZkDataListener = new IZkDataListener() {
            @Override
            public void handleDataChange(String dataPath, Object data) throws Exception {

            }
            //一旦/lock节点被删除以后,就会触发这个方法
            @Override
            public void handleDataDeleted(String dataPath) throws Exception {
                //让等待的代码不再等待了
                if (cdl != null) {
                    cdl.countDown();
                }
            }
        };
        //每一个客户端就只需要注册对前一个节点的监听
        client.subscribeDataChanges(beforePath, iZkDataListener);

        if (client.exists(beforePath)) {
            cdl = new CountDownLatch(1);
            try {
                cdl.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        client.unsubscribeDataChanges(beforePath, iZkDataListener);
    }
}

四、ZK分布式锁与redis分布式锁比较

在前面讲redis实现分布式锁时,无论怎么优化,redis分布式锁都不能满足各种极端情况下锁的安全性。那么zk实现的分布式锁,能经得住各种极端情况的考验吗?
redis分布式锁的问题是锁过期产生的一系列问题。在zk中,没有锁过期的概念,因此也避免了锁过期带来的一些问题。
但是zk分布式锁的问题是,靠临时节点来持有锁,删除临时节点代表释放锁。客户端和zk服务靠心跳保持的连接。假如网络异常,无法收到心跳,那么zk服务就认为客户端断开了连接,临时节点会被删除。如果此时锁住的代码还没执行完,就释放了锁,也会带来问题。
同样的,GC也会影响心跳的发送频率。因此也会影响分布式锁的安全性。

由此可知,在分布式环境下,没有可以做到百分之百安全的分布式锁。

综上,选择哪个作为分布式锁,就是仁者见仁,智者见智了。相比zk而已,redis的运维成本相对低一些。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Zookeeper(三)—分布式锁实现 的相关文章

  • 基于 Zipkin的链路追踪

    Zipkin介绍 Zipkin 是 Twitter 的一个开源项目 它基于 Google Dapper 实现 它致力于收集服务的定时数据 以 解决微服务架构中的延迟问题 包括数据的收集 存储 查找和展现 我们可以使用它来收集各个服务器 上请
  • dubbo配置提供者和消费者

    1 找到对应的文件 提供者 消费者 参考dubbo官网 http dubbo apache org zh cn docs user quick start html
  • 从zookeeper官方文档系统学习zookeeper

    从zookeeper官方文档系统学习zookeeper 1 zookeeper 2 zookeeper 文档 3 zookeeper 单机版 3 1 配置 3 2 启动 3 3 验证 4 zookeeper 集群版 4 1 配置 4 2 启
  • 微服务框架

    微服务框架 1 SOA思想 面向服务的架构 SOA 是一个组件模型 它将应用程序的不同功能单元 称为服务 进行拆分 并通过这些服务之间定义良好的接口和协议联系起来 接口是采用中立的方式进行定义的 它应该独立于实现服务的硬件平台 操作系统和编
  • 记录一次生产环境MySQL死锁以及解决思路

    一 背景 1 业务背景 这里因为涉及到公司的业务问题不进行深入讨论 下面换成通用的一些业务场景就是举例 2 技术背景 众所周知 所谓锁的产生本质上是想解决资源竞争问题 在MySQL的前提下 MySQL为了解决事务并发独写的问题 在进行ins
  • 2021春招已正式开启,阿里巴巴企业智能事业部内推,有意者看下文!

    前言 说一说已经拿到内推的两个朋友的面试经验 你们可以看一下准备一下 同事A阿里巴巴一面 55分钟 先介绍一下自己吧 说一下自己的优缺点 具体讲一下之前做过的项目 你觉得项目里给里最大的挑战是什么 Hashmap为什么不用平衡树 AQS知道
  • java脚本引擎Groovy实战

    前言 互联网时代随着业务的飞速发展 不仅产品迭代 更新的速度越来越快 个性化需求也是越来越多 如何快速的满足各种业务的个性化需求是我们要重点思考的问题 我们开发的系统如何才能做到热部署 不重启服务就能适应各种规则变化呢 实现业务和规则的解耦
  • 一文打通Sleuth+Zipkin 服务链路追踪

    1 为什么用 微服务架构是一个分布式架构 它按业务划分服务单元 一个分布式系统往往有很多个服务单元 由于服务单元数量众多 业务的复杂性 如果出现了错误和异常 很难去定位 主要体现在 一个请求可能需要调用很多个服务 而内部服务的调用复杂性 决
  • Kafka 权威指南

    Kafka 权威指南 这本书于 2021 年看完 2022 年又看了一遍 感觉书读百遍 其义自现 这本书侧重于 Kafka 的理论知识 虽然书有点老 但是其中关于 Kafka 的基础知识的章节讲得确实不错 适合学习 Kafka 的新手以及
  • Springboot结合Redis实现分布式定时任务

    一 背景 之前分享过分布式定时任务的技术选型方案 分布式定时任务技术选型方案 个人青睐xxl job 分享了搭建接入流程 xxl job搭建方案 本次项目需求较为简单 同时时间紧张 下面介绍利用Redis锁实现分布式定时任务的方案 二 Sc
  • 快速部署Ceph分布式高可用集群

    快速部署Ceph分布式高可用集群 Ceph简介 Ceph是一个PB EB级别的分布式存储系统 可以提供文件存储 对象存储 和块存储 它可靠性高 易扩展 管理简便 其中对象存储和块存储可以和其他云平台集成 一个Ceph集群中有Monitor节
  • 在异构系统中学习应用的流迭代分布式编码计算研究(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 参考文献 4 Matlab代码实现
  • 什么是自动化测试,看完你就懂了!

    随着互联网技术的飞速发展 软件本身的规模和复杂度也是逐步增加 为了保证软件项目能够保质保量交付到客户手中 软件测试环节就显得非常重要了 它可以看作是软件项目交付给客户最后一道安全保证 今天给大家聊聊软件测试当中自动化测试相关的知识 希望对大
  • GoLong的学习之路,进阶,微服务之使用,RPC包(包括源码分析)

    今天这篇是接上上篇RPC原理之后这篇是讲如何使用go本身自带的标准库RPC 这篇篇幅会比较短 重点在于上一章对的补充 文章目录 RPC包的概念 使用RPC包 服务器代码分析 如何实现的 总结 Server还提供了两个注册服务的方法
  • 终于找到了最新版的Zookeeper入门级教程,建议收藏!

    小熊学Java https javaxiaobear cn 1 分布式一致性 1 CAP 理论 CAP 理论指出对于一个分布式计算系统来说 不可能同时满足以下三点 一致性 在分布式环境中 一致性是指数据在多个副本之间是否能够保持一致的特性
  • 使用 Helm Chart 部署分布式 GreptimeDB

    GreptimeDB 作为云时代基础设施的时序数据库 从第一天开始就积极拥抱云原生技术 将数据库部署在 Kubernetes 上可以提供可伸缩性 自愈能力和简化的部署和管理 从而为应用程序提供了强大的弹性和可靠性 Helm 是一个用于管理
  • spark相关

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 pandas是什么 二 使用步骤 1 引入库 2 读入数据 总结 前言 提示 这里可以添加本文要记录的大概内容 例如 随着人工智能的不断发展 机器学习这门
  • 手把手教你使用HarmonyOS本地模拟器

    我们通过下面的动图来回顾下手机本地模拟器的使用效果 本期 我们将为大家介绍HarmonyOS本地模拟器的版本演进 并手把手教大家使用HarmonyOS本地模拟器 一 本地模拟器的版本演进 2021年12月31日 经过一个版本的迭代优化 随D
  • 2024年华为Harmony OS走到新里程碑:取代iOS成为国内第二大智能手机操作系统

    前言 如果要问2024年最火的技术是什么 那鸿蒙开发必须占据一些位置 HarmonyOS是华为自主研发的物联网操作系统 经历四年多的发展已构建起全新的智慧生态体系 彻底改变了智能终端的交互方式 当时鸿蒙生态的智能设备已超过7亿台 同时还带来
  • RabbitMQ环境配置

    文章目录 安装Erlang 安装RabbitMQ 安装Erlang 下载地址 http erlang org download otp win64 25 3 2 7 exe 安装RabbitMQ 下载地址 https www rabbitm

随机推荐

  • list,tensor,numpy相互转化

    使用Pytorch的过程中 经常涉及到变量需要在list numpy和tensor之间自由转化 1 1 list 转 numpy ndarray np array list 1 2 numpy 转 list list ndarray tol
  • python3.7安装tkinter模块_Mac安装tkinter模块问题解决方法

    class Python lt Formula desc Interpreted interactive object oriented programming language homepage https www python org
  • ABAP--新语法--Open SQL--第四天-- From Table

    From Table Internal Table 在 ABAP 7 52 后 支持将内表作为数据源使用 内表作为数据源使用时 需要定义别名并使用转义符 该用法可以用来代替 FOR ALL ENTRIES IN 但FROM 语句中最多使用一
  • java脚本引擎Groovy实战

    前言 互联网时代随着业务的飞速发展 不仅产品迭代 更新的速度越来越快 个性化需求也是越来越多 如何快速的满足各种业务的个性化需求是我们要重点思考的问题 我们开发的系统如何才能做到热部署 不重启服务就能适应各种规则变化呢 实现业务和规则的解耦
  • APP环信集成 -JAVA后端

    环信的集成有两种方式 一种是先创建IM账号 然后在创建客服账号 在客服账号中新建渠道中 点击关联IM账号 这样创造出的关联以IM为主 收费要收取客服和IM两项费用 官方论坛里有给出这种方式的JAVA demo这里不过的赘述 这种场景适用于类
  • object.definepProperty使用方法,vue2双向绑定原理

    首先要介绍的是definepProperty的三个参数 object definepProperty 对象名 属性名 属性值 再者要介绍的就是属性值了 object definepProperty person age value 18 此
  • 【微服务架构设计】微服务不是魔术:处理超时

    微服务很重要 它们可以为我们的架构和团队带来一些相当大的胜利 但微服务也有很多成本 随着微服务 无服务器和其他分布式系统架构在行业中变得更加普遍 我们将它们的问题和解决它们的策略内化是至关重要的 在本文中 我们将研究网络边界可能引入的许多棘
  • std::chrono::steady_clock 实现精准休眠

    include
  • 【PAT】B1032 挖掘机技术哪家强 (20 分)_C语言实现

    1 挖掘机技术哪家强 20 分 为了用事实说明挖掘机技术到底哪家强 P A T PAT PAT 组织了一场挖掘机技能大赛 现请你根据比赛结果统计出技术最强的那个学校 输入格式 输入在第 1
  • 诡异至极的SQL Server推送数据到MQ日期早48小时的生产问题排查

    背景 应用迁移 即旧版应用下线 新版应用上线 停掉旧版应用里面的quartz任务 开启新版的xxl job调度任务 数据推送源头是SQL Server 目的地是MQ 问题爆出 今天iview的自动导出任务从老系统迁移到新系统 下午2点40
  • 【设计模式】工厂模式(Factory Pattern)

    1 概述 工厂模式 Factory Pattern 是最常用的设计模式之一 它属于创建类型的设计模式 它提供了一种创建对象的最佳方式 在工厂模式中 我们在创建对象时不会对客户端暴露创建逻辑 并且是通过一个共同的接口来指向新创建的对象 工厂模
  • docker入门

    Docker基础 docker保姆级教程 https github com yeasy docker practice blob master SUMMARY md Docker系统有两个程序 docker服务端和docker客户端 其中d
  • 安装并配置HBase集群(5个节点)

    安装并配置HBase 集群规划 HBase2 2 5安装 将安装包拷贝到5台机器上并解压缩 配置环境变量 配置HBase 时间同步 修改 usr local src hbase 2 2 5 conf hbase env sh 文件 修改 h
  • SitePoint播客#61:HTML5 =厨房水槽

    Episode 61 of The SitePoint Podcast is now available This week your hosts are Patrick O Keefe iFroggy Stephan Segraves s
  • AWS动手实验 - 创建一个Web3网站

    实验操作和录播 亚马逊云科技开发者社区 web3 dApp demo README CN md at main Chen188 web3 dApp demo GitHub 注意事项 按照操作手册进行即可 需要注意到的几个地方 1 EC2 的
  • C#使用Socket建立连接、通信,主动发送Close关闭, 随后进行下一次的连接,此时会出错,通信端口被占用

    C 使用Socket建立连接 通信之后 主动发送Close关闭 随后进行下一次的连接 此时会出错 通信端口被占用 当你关闭一个Socket连接后 操作系统会在一段时间内保持该端口处于TIME WAIT状态 在这个状态下 该端口是不可用的 直
  • Qt数据类型与强制转换(转)

    类型转换 把QString转换为 double类型 方法1 QString str 123 45 double val str toDouble val 123 45 方法2 很适合科学计数法形式转换 bool ok double d d
  • java源文件命名规则

    Java程序源文件的命名不是随意的 Java文件的命名必须满足如下规则 Java程序源文件的扩展名必须是 java 不能是其他文件扩展名 在通常情况下 Java程序源文件的主文件名可以是任意的 但有一种情况例外 如果Java程序源代码里定义
  • SpringMVC加载流程

    这节介绍SpringMVC SpringMVC是一种基于Java的实现MVC设计模式的请求驱动类型的轻量级Web框架 本章会介绍相关概念 流程 再从源码进行讲解 1 MVC MVC Model View Controller 是一种软件设计
  • Zookeeper(三)—分布式锁实现

    一 独占锁原理 独占锁是利用zk同一目录下不能创建多个相同名称的节点这个特性 来实现分布式锁的功能 竞争锁的分布式系统 都在zk根目录下创建一个名为lock的节点 创建节点成功的系统 说明抢到了这把锁 没有创建成功的系统 说明这个节点已经被