集成分布式锁架包(MySQL、Redis、Zookeeper)

news2024/9/22 17:32:25

前言:

        疫情当下,大环境不好,自己又去了一家令人非常失望的单位,一直在996加班忙于业务代码,技术方面几乎等于零成长。但是,作为一个Coder,必须要挤出时间去学习与总结,不然就会被无情的淘汰。Coder加油吧!

正文:

        本篇文章主要是介绍分别使用MySQL、Redis、Zookeeper实现分布式锁的思路与代码写法,以及底层依赖框架源码原理,例如,本篇文章使用Redis实现分布式是基于Redisson框架,我会找到Redisson框架中,分布式锁的相关源码,对它进行注释和总结,方面大家理解和学习相关知识。其次,为了方便大家使用不同种类的分布式锁,我把这几种分布式锁实现集成到一个架包中,集成时,我运用了多个设计模式、例如,工厂模式、策略模式、单例模式、构造模式等。目前企业中大多数都是基于Springboot框架进行发开,我又把架包改成一个Starter,免去各自各种配置,直接引入就可以使用。下面将分别介绍不同种类分布式锁的实现与相关原理以及架包运用的设计模式等:

1. MySQL实现分布式锁:

        MySQL实现分布式锁是采用在InnoDB引擎下,使用for update排他锁完成的。 for update具有只允许获取锁的事务对数据进行更新或删除操作,其他事务想要对相同的数据再次加锁的时,会进行到阻塞状态的特点。在数据量不大的情况下,可以使用MySQL实现的分布式锁。

        使用MySQL实现的分布式锁,需要创建如下表,并且lock_key字段要有索引,不然for update 会从行锁升级成表锁:

CREATE TABLE `distribute_lock` (
    `id` bigint  NOT NULL AUTO_INCREMENT,
    `lock_key` varchar(100) NOT NULL,
    `create_time` timestamp NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `lock_key` (`lock_key`)
) ENGINE=InnoDB;

        具体实现如下(这里只展示,lock()与unlock()):

    public MysqlDistributedLock(String lockKey, DataSource dataSource) {
        super(lockKey);
        this.dataSource = dataSource;
    }

    
    @Override
    public void lock() throws DistributedLockException {
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            for (; ; ) {
                connection = dataSource.getConnection();
                connection.setAutoCommit(false);
                statement = connection.prepareStatement(SELECT_SQL);
                statement.setString(1, lockKey);
                resultSet = statement.executeQuery();
                // 存在加锁记录,并且首次执行for update,则直接return
                if (resultSet.next()) {
                    return;
                }
                close(resultSet, statement, connection);
                // 锁记录不存在创建
                Connection insertConnection = dataSource.getConnection();
                PreparedStatement insertStatement = null;
                try {
                    insertStatement = insertConnection.prepareStatement(INSERT_SQL);
                    insertStatement.setString(1, lockKey);
                    insertStatement.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
                    if (insertStatement.executeUpdate() == 1) {
                        log.info(lockKey + "的锁记录创建成功!");
                    }
                } catch (Exception e) {
                    throw new DistributedLockException("lockKey是 " + lockKey + " 加锁时,创建锁记录异常: ", e);
                } finally {
                    close(insertStatement, insertConnection);
                }
            }
        } catch (Exception e) {
            throw new DistributedLockException("lockKey是 " + lockKey + " 加锁异常: ", e);
        } finally {
            close(resultSet, statement);
        }
    }


    @Override
    public void unlock() throws DistributedLockException {
        try {
            connection.commit();
            close(connection);
        } catch (Exception e) {
            throw new DistributedLockException("lockKey是 " + lockKey + " 解锁异常: ", e);
        }
    }

2. Redis实现分布式锁:

        Redis实现分布式锁是基于Redisson框架完成的,Redisson框架在实现加锁与解锁功能都是基于Lua脚本完成的,Lua脚本在同一台Redis服务端中具有原子性的特点,可以保障脚本中所有指令原则执行。Redisson中还巧妙的运用了Redis的发布与订阅功能,可以在当前拿锁的线程解锁时,广播通知到其他抢锁线程去抢锁。Redisson还解决了加锁有效期过期的问题,引入了看门狗机制,一直为加锁线程续期。

        我具体是使用Redisson框架的RedissonLock锁,它主要用的数据结构是Hash结构,其中key是加锁key名称,field是id+":"+threadId ->UUID:threadId,value是记录可重入次数。再具体的加锁解锁原理,我日后再介绍。

        具体实现如下(这里只展示,lock()与unlock()):

    public RedisDistributedLock(String lockKey, RedissonClient client) {
        super.lockKey = lockKey;
        this.client = client;
    }


    @Override
    public void lock() throws DistributedLockException {
        try {
            client.getLock(lockKey).lock();
        } catch (Exception e) {
            throw new DistributedLockException("lockKey是 " + lockKey + " 加锁异常: ", e);
        }
    }


    @Override
    public void unlock() throws DistributedLockException {
        try {
            client.getLock(lockKey).unlock();
        } catch (Exception e) {
            throw new DistributedLockException("lockKey是 " + lockKey + " 释放锁异常: ", e);
        }
    }

3. Zookeeper实现分布式锁:

        Zookeeper实现分布式锁是基于Curator框架完成的,Curator框架的实现的分布式锁主要应用到Zookeeper的临时顺序节点、Watch模式功能。以及Java线程安全的原子类、容器和synchronized下的线程通信机制等。

        此外,Zookeeper不允许在临时节点下,创建子节点,InterProcessMutex工具类会根据加锁时,传入path的创建一个持久节点,然后在这个持久节点下创建顺序临时节点。如果,不定期清理,就会导致Zk节点数量将会急速递增。所以,启动该框架时,我会启动一个后台线程,定时去无效的节点。

        具体实现如下(这里只展示,lock()与unlock()):

    public ZkDistributedLock(String baseLockPath, String lockKey, CuratorFramework client) {
        super(lockKey);
        this.client = client;
        this.lock = getInterProcessMutex(client, getZkLockPath(baseLockPath, lockKey));
    }

    @Override
    public void lock() throws DistributedLockException {
        try {
            lock.acquire();
        } catch (Exception e) {
            throw new DistributedLockException("lockKey是 " + lockKey + " 加锁异常: ", e);
        }
    }

    @Override
    public void unlock() throws DistributedLockException {
        try {
            lock.release();
        } catch (Exception e) {
            throw new DistributedLockException("lockKey是 " + lockKey + "释放锁异常: ", e);
        }
    }

4. 集成架包运用的设计模式:

      整理的代码设计主要使用了抽象类,模板模式和工厂模式,可以通过以下类图体现:

         在启动配置架包时,运用到了建造模式,方便配置项目的相关参数。在使用具体分布式锁实现时,运用到了单例模式,获取相关配置类。

5. SpringBoot Starter:

        为了在Springboot体系下运行,我还将本项目改造成一个Springboot  Starter,可以自动获取配置,自动将DistributedLockFactory Bean 加载到Spring容器中。Springboot Starter主要是利用Spring SPI的机制,进行加载相关配置类,进行配置的自动注入。创建Springboot Starter可以参考《SpringBoot之自定义发送异常邮件Starter》

6. 源码地址:

https://gitee.com/hanxiaozhang2018/distributed-lock.git

        

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

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

相关文章

Emmet 使用 lorem 快捷生成随机文本填充html页面

快速使用 在编程中,我们也可以使用Lorem ipsum来填充页面,测试显示效果。主要是通过编辑器中自带的 Emmet 插件,识别以 lorem 开头的短语,生成指定格式的内容。以下介绍均以 vscode 为测试载体。 注意:lorem 短语需要…

Java-String 类·下

Java-String 类下5. 字符, 字节与字符串5.1 字符与字符串5.2 字节与字符串5.3 小结6.字符串常见操作6.1 字符串比较6.2 字符串查找6.3 字符串替换6.4 字符串拆分6.5 字符串截取6.6 其他操作方法7. StringBuffer 和 StringBuilder补充大家好,我是晓星航。今天为大家带…

计算机网络体系结构

目录常见的计算机网络体系结构计算机网络体系结构分层的必要性计算机网络体系结构分层思想举例计算机网络体系结构中的专用术语常见的计算机网络体系结构 TCP/IP体系结构相当于将OSI体系结构的物理层和数据链路层合并为网络接口层。并去掉了会话层和表示层。 由于TCP/IP在网络…

Java爬虫 爬取某招聘网站招聘信息

Java爬虫 爬取某招聘网站招聘信息一、系统介绍二、功能展示1.需求爬取的网站内容2.实现流程2.1数据采集2.2页面解析2.3数据存储三、获取源码一、系统介绍 系统主要功能:本项目爬取的XX招聘网站 二、功能展示 1.需求爬取的网站内容 2.实现流程 爬虫可以分为三个模…

[Kettle] Kettle界面介绍

启动Kettle后,弹出Kettle的欢迎界面 有关界面的构成和说明如下所示 ①标题栏:显示界面标题名称 ②菜单栏:分别有【文件】|【编辑】|【视图】|【执行】|【工具】|【帮助】六个菜单栏 ③工具图标栏:显示图形化的常用和重要的菜单项…

SAP MM采购定价过程字段解析

下面我们针对每一个字段进行解释和用途分析 : 1、 步骤:代表了创建PO时,哪个条件类型放到前面,哪个放到后面,如果步骤号相同,那就以谁先选择出来谁就在前面。 2、 计数:没有任何实际意义&a…

DaVinci:神奇遮罩

调色页面:神奇遮罩Color:Magic Mask神奇遮罩 Magic Mask基于人工智能技术,在检视器中绘制一个笔画,就能识别出笔画所在的对象,并以此自动创建遮罩。先确定要对画面上的物体还是人体做遮罩。若是对人体做遮罩&#xff0…

【闲来无聊写个几个小特效——五角星,小光圈,探照灯】

五角星,见过吧,如果是你,你如何使用代码写一个五角星呢?思考一下,你会说,先这样在那样就好啦,可是真正上手的时候却修修改改磕磕绊绊来看一下今天的五角星如何用几行代码实现 1.绘制五角星 四行…

Pytorch进行自定义Dataset 和 Dataloader 原理

1、自定义加载数据 在pytorch中,数据集对象被抽象为Dataset类,实现自定义的数据集需要继承Dataset,并实现相应的方法。 在学习Pytorch的教程时,加载数据许多时候都是直接调用torchvision.datasets里面集成的数据集,直…

GO第 4 章:运算符

第 4 章 运算符 4.1 运算符的基本介绍 运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等 运算符是一种特殊的符号,用以表示数据的运算、赋值和比较等 算术运算符 赋值运算符 比较运算符/关系运算符 逻辑运算符 位运算符 其它运算 4.2 …

Java开发环境安装

总步骤 第一步:安装JDK(Java Development Kit,Java软件开发工具包) 第二步:安装IDEA(是Java语言的集成开发环境) 一、安装JDK Windows下最简单的Java环境安装指南 - 大博哥VV6 - 博客园 (cnblo…

微信小程序框架

框架 小程序开发框架的目标是通过尽可能简单、高效的方式让开发者可以在微信中开发具有原生 APP 体验的服务。 整个小程序框架系统分为两部分:逻辑层(App Service)和 视图层(View)。小程序提供了自己的视图层描述语言…

【Linux】进程创建、进程终止和进程等待

​🌠 作者:阿亮joy. 🎆专栏:《学会Linux》 🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根 目录👉进程创建&…

力扣刷题记录——231. 2 的幂、228. 汇总区间、242. 有效的字母异位词

本专栏主要记录力扣的刷题记录,备战蓝桥杯,供复盘和优化算法使用,也希望给大家带来帮助,博主是算法小白,希望各位大佬不要见笑,今天要分享的是——《231. 2 的幂、228. 汇总区间、242. 有效的字母异位词》。…

【王道操作系统】2.2.4 作业进程调度算法(FCFS先来先服务、SJF短作业优先、HRRN高响应比优先)

作业进程调度算法(FCFS先来先服务、SJF短作业优先、HRRN高响应比优先) 文章目录作业进程调度算法(FCFS先来先服务、SJF短作业优先、HRRN高响应比优先)1.先来先服务(FCFS)2.短作业优先(SJF)3.高响应比优先(HRRN)4.三种算法的对比和总结1.先来先服务(FCFS) 先来先服务调度算法(F…

区间选点 and 最大不相交区间

区间选点 题目描述 给定 N 个闭区间 [ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。 输出选择的点的最小数量。 位于区间端点上的点也算作区间内。 输入输出及样例 最大不相交区间 题目描述 给定 N 个闭区间 [ai,bi]&…

ArcGIS基础实验操作100例--实验32计算栅格行列号

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台:ArcGIS 10.6 实验数据:请访问实验1(传送门) 高级编辑篇--实验32 计算栅格行列号 目录 一、实验背景 二、实验数据 三、实验步骤 (1&am…

GPU存储器架构-- 全局内存 本地内存 寄存器堆 共享内存 常量内存 纹理内存

上表表述了各种存储器的各种特性。作用范围栏定义了程序的哪个部分能使用该存储器。而生存期定义了该存储器中的数据对程序可见的时间。除此之外,Ll和L2缓存也可以用于GPU程序以便更快地访问存储器。 总之,所有线程都有一个寄存器堆,它是最快…

【PDPTW】python调用guribo求解PDPTW问题(Li Lim‘s benchmark)之二

原文连接:知乎《使用Python调用Gurobi求解PDPTW问题(Li & Lim’s benchmark)》 分析文章:文章目录修改utlis.pytest.py运行DataPath"lc101.txt"修改 以及修改公示约束(8)与代码不符合的问题…

【QT开发笔记-基础篇】| 第五章 绘图QPainter | 5.13 抗锯齿

本节对应的视频讲解:B_站_视_频 https://www.bilibili.com/video/BV1YP4y1B7Ex 本节讲解抗锯齿效果 前面实现的效果中,仔细观看能看到明显的锯齿的效果,如下: 此时,可以增加抗锯齿的效果。 1. 关联信号槽 首先&…