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,或者是时间戳,至于实现方式大家可以自己实现一下,这里就不做赘述了

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

03.DDD六边形架构

学习视频来源:DDD独家秘籍视频合集 https://space.bilibili.com/24690212/channel/collectiondetail?sid1940048&ctype0 文章目录 什么是依赖DDD四层架构六边形架构代码实现 想要详细了解六边形架构,可以看我之前的一篇文章。是对六边形架构原文的翻…

前端开发实现自定义勾选/自定义样式,可复选,可取消勾选

基于后端返回数组实现多选、复选 以下代码基于vue2&#xff0c;如果有需要React/Vue3或者其他框架代码的&#xff0c;可以通过国内直连GPT4o进行代码转换&#xff0c;转换正确率99% 前端代码如下(直接拷贝到你的vue代码即可)&#xff1a; <!-- CustomCheckboxList.vue --&g…

新型智慧城市顶层设计方案(118页word)

文档介绍&#xff1a; 新型智慧城市顶层设计方案是一种全局性、前瞻性的规划&#xff0c;旨在通过整合城市各类资源&#xff0c;运用新一代信息技术&#xff0c;推动城市治理、民生服务、产业发展等领域的全面升级&#xff0c;以实现城市的可持续发展和居民生活质量的提升。该…

nginx-proxy-manager实现反向代理+自动化证书(实战)

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 &#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交给时间 &#x1f3e0; &#xff1a;小破站 cnginx-proxy-manager实现反向代理自动化证书 nginx-proxy-manager是什么搭建nginx-proxy-manage…

定时器入门:Air780E定时器基础与进阶

今天我们学习的是Air780E定时器基础与进阶&#xff0c;让大家更深入的了解定时器。 一、定时器(timer)的概述 在Air780E模组搭载的LuatOS系统中&#xff0c;定时器&#xff08;timer&#xff09;是一项基础且关键的服务。它允许开发者在特定的时间点或周期性地执行代码段&…

C语言复习第7章 自定义类型(结构体+位段+枚举+联合体)

目录 一、结构体1.1 内置类型和自定义类型1.2 结构体的概念1.3 结构体基本的声明1.4 区分两种创建结构体变量的方式1.5 结构体变量的定义和初始化1.6 区分一下typdef和变量列表1.7 匿名结构体类型1.8 访问结构体成员1.9 修改字符数组成员变量的时候 要用strcpy1.10 结构体的传参…

Twitter(X)2024最新注册教程

Twitter 现名为X&#xff0c;因为图标是一只小鸟的形象&#xff0c;大家也叫它小蓝鸟&#xff08;埃隆马斯克于 2023 年对该平台进行了品牌重塑&#xff09;&#xff0c;目前仍然是全球最受欢迎的社交媒体和微博平台之一&#xff0c;全球活跃用户量大概在4.5亿。尤其是欧美国家…

[单例模式]

[设计模式] 设计模式是软件工程中的一种常见做法, 它可以理解为"模板", 是针对一些常见的特定场景, 给出的一些比较好的固定的解决方案. 不同语言适用的设计模式是不一样的. 这里我们接下来要谈到的是java中典型的设计模式. 而且由于设计模式比较适合有一定编程经…

[mysql]DDL,DML综合案例,

综合案例 题目如下 目录 综合案例 ​编辑 ​编辑 # 1、创#1建数据库test01_library # 2、创建表 books&#xff0c;表结构如下&#xff1a; # 3、向books表中插入记录库存 # 4、将小说类型(novel)的书的价格都增加5。 # 5、将名称为EmmaT的书的价格改为40&#xff0c;并将…

day-81 打家劫舍 II

思路 与LCR 089. 打家劫舍相比&#xff0c;本题所有房屋围成了一圈&#xff0c;那么第一间房子和最后一间房子不能同时打劫&#xff0c;那么就可以分为两种情况&#xff1a;1.选第一间房打劫&#xff1b;2.选最后一间房打劫 解题过程 然后依次计算出以上两种情况的最大金额&am…

秃姐学AI系列之:GRU——门控循环单元 | LSTM——长短期记忆网络

RNN存在的问题 因为RNN模型的BPTT反向传导的链式求导&#xff0c;导致需要反复乘以一个也就是说会出现指数级别的问题&#xff1a; 梯度爆炸&#xff1a;如果的话&#xff0c;那么连乘的结果可能会快速增长&#xff0c;导致梯度爆炸梯度消失&#xff1a;如果的话&#xff0c;…

OpenHarmony 入门——ArkUI 自定义组件间的父子双向同步状态装饰器@Link语法(四)

文章大纲 引言一、组件间状态装饰器Link 父子双向同步1、使用规则2、支持的观察变化的场景和ArkUI 刷新UI3、Link变量值初始化和更新机制3.1、初始渲染&#xff1a;执行父组件的build()函数后将创建子组件的新实例。3.2、Link的数据源的更新&#xff1a;即父组件中状态变量更新…

机器学习与数据挖掘_使用梯度下降法训练线性回归模型

目录 实验内容 实验步骤 1. 导入必要的库 2. 加载数据并绘制散点图 3. 设置模型的超参数 4. 实现梯度下降算法 5. 打印训练后的参数和损失值 6. 绘制损失函数随迭代次数的变化图 7. 绘制线性回归拟合曲线 8. 基于训练好的模型进行新样本预测 实验代码 实验结果 实验…

机器学习与AI|如何利用数据科学优化库存周转率?

对于所有零售商来说&#xff0c;良好的库存管理都是非常重要的。众所周知&#xff0c;商品如果不放在货架上就无法出售&#xff0c;而如果库存过多则意味着严重的财务负担。 但是做好库存管理绝非易事&#xff0c;它依赖于对未来需求的准确预测和确保始终有合适库存的敏捷供应链…

Proteus中数码管动态扫描显示不全(已解决)

文章目录 前言解决方法后记 前言 我是直接把以前写的 51 数码管程序复制过来的&#xff0c;当时看的郭天祥的视频&#xff0c;先送段选&#xff0c;消隐后送位选&#xff0c;最后来个 1ms 的延时。 代码在 Proteus 中数码管静态是可以的&#xff0c;动态显示出了问题——显示…

如何快速搭建一个spring boot项目

一、准备工作 1.1 安装JDK&#xff1a;确保计算机上已安装Java Development Kit (JDK) 8或更高版本、并配置了环境变量 1.2 安装Maven&#xff1a;下载并安装Maven构建工具&#xff0c;这是Spring Boot官方推荐的构建工具。 1.3 安装代码编辑器&#xff1a;这里推荐使用Inte…

基于ViT的无监督工业异常检测模型汇总

基于ViT的无监督工业异常检测模型汇总 论文1&#xff1a;RealNet: A Feature Selection Network with Realistic Synthetic Anomaly for Anomaly Detection&#xff08;2024&#xff09;1.1 主要思想1.2 系统框架 论文2&#xff1a;Inpainting Transformer for Anomaly Detecti…

数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看

前言 这个专栏将会用纯C实现常用的数据结构和简单的算法&#xff1b;用C基础即可跟着学习&#xff0c;代码均可运行&#xff1b;准备考研的也可跟着写&#xff0c;个人感觉&#xff0c;如果时间充裕&#xff0c;手写一遍比看书、刷题管用很多&#xff0c;这也是本人采用纯C语言…

Python | Leetcode Python题解之第542题01矩阵

题目&#xff1a; 题解&#xff1a; class Solution:def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]:m, n len(matrix), len(matrix[0])# 初始化动态规划的数组&#xff0c;所有的距离值都设置为一个很大的数dist [[10**9] * n for _ in range(m)]…

ENSP作业——园区网

题目 根据上图&#xff0c;可得需求为&#xff1a; 1.配置交换机上的VLAN及IP地址。 2.设置SW1为VLAN 2/3的主根桥&#xff0c;设置SW2为VLAN 20/30的主根桥&#xff0c;且两台交换机互为主备。 3.可以使用super vlan。 4.上层通过静态路由协议完成数据通信过程。 5.AR1作为企…