mysql分布式锁

news2024/11/12 10:05:57

大家好,今天我们来看下如何使用本地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,或者是时间戳,至于实现方式大家可以自己实现一下,这里就不做赘述了

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

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

相关文章

洞察鸿蒙生态,把握开发新机遇

随着科技的不断进步,鸿蒙系统以其独特的分布式架构和跨设备协同能力,逐渐在智能手机、智能穿戴、车载、家居等多个领域崭露头角,与安卓、iOS形成三足鼎立之势。作为一名开发者,我对鸿蒙生态的认知和了解如下: 一、鸿蒙…

Node.js 全栈开发进阶篇

​🌈个人主页:前端青山 🔥系列专栏:node.js篇 🔖人终将被年少不可得之物困其一生 依旧青山,本期给大家带来node.js篇专栏内容:node.js- 全栈开发进阶篇 前言 大家好,我是青山。在上一篇文章中,…

VS Code 插件 MySQL Shell for VS Code

https://marketplace.visualstudio.com/items?itemNameOracle.mysql-shell-for-vs-code

2024年云手机推荐榜单:高性能云手机推荐

无论是手游玩家、APP测试人员,还是数字营销工作者,云手机都为他们带来了极大的便利。本文将为大家推荐几款在市场上表现优异的云手机,希望这篇推荐指南可以帮助大家找到最适合自己的云手机! 1. OgPhone云手机 OgPhone云手机是一款…

「QT」QT5程序设计专栏目录

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

VMWARE ESXI VMFS阵列故障 服务器数据恢复

1:河南用户一台DELL R740 3块2.4T硬盘组的RAID5,早期坏了一个盘没有及时更换,这次又坏了一个,导致整组RAID5处于数据丢失的状态, 2:该服务器装的是VMware ESXI 6.7,用户把3块硬盘寄过来进行数据…

怎么对 PDF 添加权限密码或者修改密码-免费软件分享

序言 目前市面上有关PDF处理的工具有很多,不过绝大多数的PDF处理工具都需要付费使用,且很多厂商甚至连试用的机会也不给用户,偶有试用的,其试用版的条件也极为苛刻,比如只能处理前两页,或者只能处理非常小的…

轻松上云:使用Python与阿里云OSS实现文件上传

轻松上云:使用Python与阿里云OSS实现文件上传 ​ 在数字化时代,数据的存储和管理变得越来越重要。阿里云对象存储服务(OSS)提供了一种高效、安全的方式来存储和访问各种类型的文件。本文将介绍如何利用Python编程语言结合阿里云O…

通过包控制->获取包重新获取之后,需求类型列表不对

龙勤思(2017年11月27日): 这个类型列表,我在把需求包提交到svn,再新建一个eap,通过包控制->获取包重新获取之后,就变成默认的如下列表了。我从你的原始的eap导出参考数据,再导入到新建的eap&#xff0c…

python+pptx:(三)添加统计图、删除指定页

目录 统计图 删除PPT页 from pptx import Presentation from pptx.util import Cm, Inches, Mm, Pt from pptx.dml.color import RGBColor from pptx.chart.data import ChartData from pptx.enum.chart import XL_CHART_TYPE, XL_LABEL_POSITION, XL_DATA_LABEL_POSITIONfil…

基础概念理解

一,数据结构分类 连续结构,跳转结构。 二,对变量的理解 在 C 语言中,变量是用于存储数据的抽象符号。变量本质上是一块内存区域的标识符(即它代表内存中的某一块区域),用来存储数据&#xff…

【微服务】不同微服务之间用户信息的获取和传递方案

如何才能在每个微服务中都拿到用户信息?如何在微服务之间传递用户信息? 文章目录 概述利用微服务网关做登录校验网关转微服务获取用户信息openFeign传递微服务之间的用户信息 概述 要在每个微服务中获取用户信息,可以采用以下几种方法&#…

5G NR:各物理信道的DMRS配置

DMRS简介 在5G中,DMRS(DeModulation Reference Signal)广泛存在于各个重要的物理信道当中,如下行的PBCH,PDCCH和PDSCH,以及上行的PUCCH和PUSCH。其最为重要的作用就是相干解调(Coherence Demodu…

使用Docker快速部署FastAPI Web应用

Docker是基于 Linux 内核的cgroup、namespace以及 AUFS 类的Union FS 等技术,对进程进行封装隔离,一种操作系统层面的虚拟化技术。Docker中每个容器都基于镜像Image运行,镜像是容器的只读模板,容器是模板的一个实例。镜像是分层结…

「QT」几何数据类 之 QRectF 浮点型矩形类

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

2024双十一有什么是宝妈们值得入手的?双十一母婴必买清单

随着双十一购物狂欢节的临近,宝妈们纷纷开始筹备为家庭增添新的宝贝。作为一年一度的大型促销活动,双十一不仅提供了各种优惠,更是宝妈们囤货的好时机。2024双十一有什么是宝妈们值得入手的?在这个特殊的日子里,母婴产…

VMware Fusion和centos 8的安装

资源 本文用到的文件:centos8镜像 , VMware 软件包 , Termius 文件链接: https://pan.baidu.com/s/1kOES_ZJ8NGN-BnJl6NC7Sg?pwd63ct 安装虚拟机 先 安装 vmware ,然后打开,将下载的 iso 镜像拖入 拖入镜像文件iso Continue, 然后随便选…

返回对象的唯一标识符通常是对象的内存地址id(对象或变量)

【小白从小学Python、C、Java】 【考研初试复试毕业设计】 【Python基础AI数据分析】 返回对象的唯一标识符 通常是对象的内存地址 id(对象或变量) [太阳]选择题 根据题目代码,执行的结果是? a [1, 2, 3] b a c a.copy() print("【显示】id(a) &…

SAP ABAP开发学习——WDA 四

目录 页面技术的发展 WebDynpro Layout控件 Layout的组件结构 布局方式 流式布局FlowLayout ​编辑 行布局RowLayout 矩阵布局MatrixLayout 网格布局GridLayout 数据绑定 在屏幕上显示数据 数据的双向传输 通过数据绑定控制UI显示 属性节点类型 属性的数据类型 …

速度快还看巡飞,筒射巡飞无人机技术详解

筒射巡飞无人机(Launch and Recovery by Tube,LRAT或Launcher-Deployed Loitering Munition,LDLM)作为一种新型无人机系统,近年来在军事和民用领域都展现出了巨大的潜力。以下是对筒射巡飞无人机技术的详细解析&#x…