mysql分布式锁

大家好,今天我们来看下如何使用本地MySql实现一把分布式锁,以及Mysql实现分布式锁的原理是怎么样的

MySql实现分布式锁有三种方式

1:基于行锁实现分布式锁
k1.png

实现原理
首先我们的表lock要提前存好相对应的lockName,这时候多个客户端来执行

select lock_name from lock where lock_name = #{lockName} for update
由于第一个客户端来执行这条sql语句,给这行记录加了行锁,在这个客户端没有提交事务之前,其它客户端就会被阻塞住。所以这时候就只能有一个客户端去执行我们自己的业务了,其它客户端就只能阻塞等待,那么这个过程就是加锁

那么释放锁该怎么操作呢?

其实释放锁就很简单了,也就是将获取到锁的这个客户端的事务提交,这样其它客户端就可以来获取到这把行锁了,所以这时候就需要我们手动的提交事务了

代码实现
首先就是编写我们的加锁SQL语句了

@Select(“select lock_name from lock where lock_name = #{lockName} for update”)
List queryLockNameForUpdate(@Param(“lockName”) String lockName);
然后我们需要实现我们的加锁 和 解锁

public class MySqlDistributeLock {

//加锁的KEY,也就是我们提前存到表lock的值
private String lockName;

//手动提交事务需要的事务管理器,由外部传入
private DataSourceTransactionManager dataSourceTransactionManager;

//自定义编写的mybatis的mapper文件
private MySqlLockMapper mySqlLockMapper;

private TransactionStatus status;

public MySqlDistributeLock(String lockName,DataSourceTransactionManager dataSourceTransactionManager,MySqlLockMapper mySqlLockMapper) {
    this.lockName = lockName;
    this.dataSourceTransactionManager = dataSourceTransactionManager;
    this.mySqlLockMapper = mySqlLockMapper;
}



public void lock() {
    TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
    status = dataSourceTransactionManager.getTransaction(transactionDefinition);
    while (true) {
        try{
            mySqlLockMapper.queryLockNameForUpdate(this.lockName);
            //如果加锁成功,就退出该循环
            break;
        }catch (Exception e) {
            //说明抛出异常了,让线程重试
            try {
                //让线程休眠一会
                Thread.sleep(100);
            } catch (InterruptedException ignored) { }
        }
    }
}


public void unLock() {
    //手动提交事务,也就是释放锁
    dataSourceTransactionManager.commit(this.status);
}

}
最后看下业务方如何使用

@Service
public class LockService {

@Resource
private DataSourceTransactionManager dataSourceTransactionManager;

@Resource
private MySqlDistributeLock.MySqlLockMapper mySqlLockMapper;



public String deductStockMysqlLock(String productId,Integer count) {
    MySqlDistributeLock lock = null;
    try{
        lock = new MySqlDistributeLock(productId,dataSourceTransactionManager,mySqlLockMapper);
        //加锁
        lock.lock();

        //加锁成功,开始执行我们自己的业务逻辑
    }finally {
        if(lock != null) {
            lock.unLock();
        }
    }
    return "success";
}

}
2:基于唯一索引实现分布式锁
k2.png

实现原理
首先我们的lock表要给lock_name字段建立一个唯一索引,这时候有多个客户端来加锁,本质上也就是添加一条记录,只不过lockName的值都是一样的

这时候客户端A成功的把lockName保存到lock表中了,那么其它客户端要保存这个lockName的时候(也就是执行加锁),由于唯一索引的缘故,就会插入失败。也就保证了同一个时间只能有一个客户端保存成功,也就是加锁成功了

那么如何释放锁呢?

在这个客户端业务执行完之后,手动的把这条记录删除掉,那么其它客户端就可以来继续加锁了

代码实现
首先我们在mapper文件中编写 加锁 和 解锁 的SQL,这里为什么还要保存个uuid,后续会讲到(主要是防止锁被误删)

//加锁语句
@Insert(“insert into record_lock (lock_name, uuid) values (#{lockName}, #{uuid})”)
Integer insert(@Param(“lockName”) String lockName, @Param(“uuid”) String uuid);

//解锁语句
@Delete(“delete from record_lock where lock_name = #{lockName} and uuid = #{uuid}”)
Integer delete(@Param(“lockName”) String lockName, @Param(“uuid”) String uuid);
然后我们需要实现我们的加锁 和 解锁

public class MySqlDistributeLock {

private String lockName;

//自定义编写的mybatis的mapper文件
private MySqlLockMapper mySqlLockMapper;

private String uuid;


public MySqlDistributeLock(String lockName,MySqlLockMapper mySqlLockMapper,String uuid) {
    this.lockName = lockName;
    this.mySqlLockMapper = mySqlLockMapper;
    this.uuid = uuid;
}



public void lock() {
    while (true) {
          try{
              int result = mySqlLockMapper.insert(this.lockName, this.uuid);
              if(result > 0) {
                  //代表加锁成功
                  break;
              }
          } catch (Exception e) {
          }

        //唯一索引加锁失败
        try {
            Thread.sleep(100);
        } catch (InterruptedException interruptedException) {
            throw new RuntimeException();
        }
    }
}


public void unLock() {
    mySqlLockMapper.delete(this.lockName,this.uuid);
}

}
最后看下业务方如何使用

@Service
public class LockService {

@Resource
private MySqlDistributeLock.MySqlLockMapper mySqlLockMapper;



public String deductStockMysqlLock(String productId,Integer count) {
    MySqlDistributeLock lock = null;
    try{
        lock = new MySqlDistributeLock(productId, mySqlLockMapper,UUID.randomUUID().toString());
        //加锁
        lock.lock();

        //加锁成功,开始执行我们自己的业务逻辑
    }finally {
        if(lock != null) {
            lock.unLock();
        }
    }
    return "success";
}

}
基于唯一索引实现的分布式锁有没有什么问题呢??

死锁问题
我们试想一下,如果客户端A来加锁成功了,业务也执行完了,但是这时候释放锁的时候,也就是执行删除语句的时候因为一些原因导致删除失败了,那么这条记录一直存在,后续的线程就没办法再获取到锁了,这就是所谓的死锁

所以这时候我们还需要另外一个服务来定时扫描这些记录,如果这个记录超过了10分钟,或者20分钟还没有被删除掉,那么大概率是释放锁的时候失败了,所以需要再次删除这条记录

锁误删
为什么锁会误删呢? 为了防止死锁,我们会有一个单独的定时任务来扫描,假设我们判断一把锁超过10分钟就认为是释放锁失败了,这时候定时任务就会把这条记录删除掉,但是这时候就会有问题了,举个例子

客户端A首先获取到锁了,然后开始执行业务,但是因为业务比较复杂,执行完业务可能需要15分钟,这时候到第10分钟的时候,定时任务就会把这条记录给删除掉了

这时候因为记录没有了,客户端B来获取锁是能成功获取到的,所以这时候这把锁的持有者应该是客户端B的

到第15分钟的时候,客户端A业务执行完了,就是执行释放锁的逻辑,那么客户端A就会把这条记录给删除掉了,也就导致客户端A把客户端B的锁给释放掉了

所以在开头的时候,我们加锁除了要保存lockName,还要保存一个uuid,在释放锁的时候,判断一下uuid是否相等,如果不相等,那就不能删除这条记录了,因为这时候这把锁已经不是当前客户端持有的了

锁续期
大家可以想一下,分布式锁的主要目的就是同一个时间点只能有一个线程去执行业务,但是在上面我们可以看到,即使加了uuid来保证了锁误删,但是在 同一个时间点可能是有多个线程在一起执行业务的,为了避免这种情况,就需要保证一个客户端在没有执行完业务以前,是不允许其它客户端执行业务的

但是定时任务判断的时间我们没办法预估,可能业务需要10分钟,也有可能是20分钟,我们没办法准确预估这个时间

所以我们在一个客户端加锁成功之后,可以起一个额外的线程,时时的更新加锁的时间,这就类似Redisson的看门狗机制了,那么如何去做呢??

1:加锁的时候,除了保存lockName,uuid,额外保存一个加锁时间lockTime
2:加锁成功之后,额外开启一个线程,每过10秒就更新lockTime为当前时间
3:定时任务扫描到lcokTime距离当前时间超过10分钟或者5分钟的记录就删除掉这条记录
3:基于乐观锁实现分布式锁
基于乐观锁机制就是依靠版本机制来实现,我们一般在数据库会保存version,或者是时间戳,至于实现方式大家可以自己实现一下,这里就不做赘述了

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

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

相关文章

手机内卷下一站,AI Agent

作者 | 辰纹 来源 | 洞见新研社 2024年除夕夜,OPPO在央视春晚即将开始前举办了一场“史上最短发布会”,OPPO首席产品官刘作虎宣布,“OPPO正式进入AI手机时代”。 春节假期刚过,魅族又公开表示,将停止“传统智能手机…

A021基于Spring Boot的自习室管理和预约系统设计与实现

🙊作者简介:在校研究生,拥有计算机专业的研究生开发团队,分享技术代码帮助学生学习,独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取,记得注明来意哦~🌹 赠送计算机毕业设计600…

浮点数二进制在线转换器

具体请前往:浮点数在线转二进制工具--在线将10进制浮点数(float)转化为4字节32位的二进制序列

探索 Python 的新边疆:sh 库的革命性功能

文章目录 **探索 Python 的新边疆:sh 库的革命性功能**第一部分:背景介绍第二部分:sh 库是什么?第三部分:如何安装 sh 库?第四部分:简单库函数使用方法1. 执行 ls 命令2. 使用 grep 搜索文件内容…

【GeoJSON在线编辑平台】(2)吸附+删除+挖孔+扩展

前言 在上一篇的基础上继续开发,补充上吸附功能、删除矢量、挖孔功能。 实现 1. 吸附 参考官方案例:Snap Interaction 2. 删除 通过 removeFeature 直接移除选中的要素。 3. 挖孔 首先是引入 Turf.js ,然后通过 mask 方法来实现挖孔的…

吾店云介绍 – 中国人的WordPress独立站和商城系统平台

经过多年在WordPress建站领域的摸索和探索,能轻松创建和管理各种类型网站的平台 – 吾店云建站平台诞生了。 应该说这是一个艰苦卓绝的过程,在中国创建一个能轻松创建和使用WordPress网站的平台并不容易,最主要是网络环境和托管软件的限制。…

【动手学电机驱动】STM32-FOC(6)基于 IHM03 的无感方波控制

STM32-FOC(1)STM32 电机控制的软件开发环境 STM32-FOC(2)STM32 导入和创建项目 STM32-FOC(3)STM32 三路互补 PWM 输出 STM32-FOC(4)IHM03 电机控制套件介绍 STM32-FOC(5&…

94.【C语言】数据结构之双向链表的初始化,尾插,打印和尾删

目录 1.双向链表 2.结构体的定义 3.示意图 3.代码示例 1.双向链表的尾插 示意图 代码 main.c List.h List.c 详细分析代码的执行过程 双向链表的初始化 2.双向链表的打印 代码 3.双向链表的尾删 1.双向链表 以一种典型的双向链表为例:带头双向循环链表(带头:带…

「QT」几何数据类 之 QPoint 整型点类

✨博客主页何曾参静谧的博客📌文章专栏「QT」QT5程序设计📚全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasolid…

黑马点评1 session实现短信验证码登录

1 什么是session cookie的session的应用场景:cookie可以用来保存用户的登陆信息,如果删除cookie则下一次用户仍需要重新登录 session就类似于我们拿到钥匙去开锁,拿到的就是我们个人的信息,一般我们可以在session中存放个人的信息…

[Docker#3] LXC | 详解安装docker | docker的架构与生态

目录 1.LXC容器操作 安装LXC LXC容器操作步骤 2.理论 LXC 是什么? Docker 是什么 Docker 和虚拟机的区别 Docker 和 JVM 虚拟化的区别 Docker 版本 ⭕Docker 官方网站(建议收藏) Docker 架构 生活案例 Docker 生态 Docker 解决…

MySQL数据库专栏(五)连接MySQL数据库C API篇

摘要 本篇文章主要介绍通过C语言API接口链接MySQL数据库,各接口功能及使用方式,辅助类的封装及调用实例,可以直接移植到项目里面使用。 目录 1、环境配置 1.1、添加头文件 1.2、添加库目录 2、接口介绍 2.1、MySql初始化及数据清理 2.1.…

从0开始深度学习(27)——卷积神经网络(LeNet)

1 LeNet神经网络 LeNet是最早的卷积神经网络之一,由Yann LeCun等人在1990年代提出,并以其名字命名。最初,LeNet被设计用于手写数字识别,最著名的应用是在美国的邮政系统中识别手写邮政编码。LeNet架构的成功证明了卷积神经网络在…

如何用C#和Aspose.PDF实现PDF转Word工具

在本篇博文中,我将详细讲解如何用C#实现一个PDF转Word工具。这款工具基于Aspose.PDF库,实现PDF文件转为Word(DOC/DOCX)格式的功能,并通过用户友好的界面和状态提示提升用户体验。希望通过这篇文章帮助大家理解软件的实…

运维技术之文件系统(File System for 0peration and Maintenance Technology)

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 本人主要分享计算机核心技…

IoTDB 与 HBase 对比详解:架构、功能与性能

五大方向,洞悉 IoTDB 与 HBase 的详尽对比! 在物联网(IoT)领域,数据的采集、存储和分析是确保系统高效运行和决策准确的重要环节。随着物联网设备数量的增加和数据量的爆炸式增长,开发者和决策者们需要选择…

【Vue】Vue2和Vue3响应式原理

前言 Vue 3 的核心部分可以分为三个主要模块:Compiler、Reactivity 和 Runtime。响应式的处理逻辑在 Reactivity 部分。 Compiler(编译器):Template > 渲染函数 将 Vue 的模板(Template)转换成 JavaS…

哪些人群适合考取 PostgreSQL 数据库 PGCM 证书?

#postgresql#,作为开源数据库领域的佼佼者,凭借其强大的功能和广泛的应用场景,吸引了大量数据库从业者的关注。它代表着持有者在PostgreSQL数据库管理、优化、安全和高可用性设计等方面的专家级技能。 PGCM证书适合那些具备扎实理论基础和一…

C++高级编程(9)

九、STL模板库 1.C函数模板 函数模板是一个独立于类型的函数,可产生函数特定类型的版本。通过对参数类型进行参数化,获取有相同形式的函数体。 它是一个通用函数,它可适应一定范围内的不同类型对象的操作。 函数模板将代表着不同类型的一组…

深圳世界之窗:文化与娱乐交织的旅游胜地

深圳世界之窗位于广东省深圳市南山区华侨城,是中国著名的缩微景区。它以弘扬世界文化为宗旨,将世界奇观、历史遗迹、民间歌舞表演、高科技游乐项目等融为一体,为游客打造出一个不出国门就能领略世界风情的旅游胜地。 从文化角度来看&#xff…