MySQL做分布式锁

分布式锁mysql实现方式
方式1:唯一索引

  • 创建锁表,内部存在字段表示资源名及资源描述,同一资源名使用数据库唯一性限制。
  • 多个进程同时往数据库锁表中写入对某个资源的占有记录,当某个进程成功写入时则表示其获取锁成功
  • 其他进程由于资源字段唯一性限制插入失败陷入自旋并且失败重试。
  • 当执行完业务后持有该锁的进程则删除该表内的记录,此时回到步骤一。
    在这里插入图片描述
    表数据
create table `database_lock`(`id` BIGINT NOT NULL AUTO_INCREMENT,`resource` INT NOT NULL COMMENT '锁资源',`description` varchar(1024) NOT NULL DEFAULT "" COMMENT '描述',PRIMARY KEY (`id`),UNIQUE KEY `resource` (`resource`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库分布式锁表';

db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/distribute_lock?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai
user=root
password=123456
  • PropertiesReader
@Slf4j
public class PropertiesReader {// Properties缓存文件private static final Map<String, Properties> propertiesCache = new HashMap<String, Properties>();public static Properties getProperties(String propertiesName) throws IOException {if (propertiesCache.containsKey(propertiesName)) {return propertiesCache.get(propertiesName);}loadProperties(propertiesName);return propertiesCache.get(propertiesName);}private synchronized static void loadProperties(String propertiesName) throws IOException {FileReader fileReader = null;try {// 创建Properties集合类Properties pro = new Properties();// 获取src路径下的文件--->ClassLoader类加载器ClassLoader classLoader = PropertiesReader.class.getClassLoader();URL resource = classLoader.getResource(propertiesName);// 获取配置路径String path = resource.getPath();// 读取文件fileReader = new FileReader(path);// 加载文件pro.load(fileReader);// 初始化propertiesCache.put(propertiesName, pro);} catch (IOException e) {log.error("读取Properties文件失败,Properties名为:" + propertiesName);throw e;} finally {try {if (fileReader != null) {fileReader.close();}} catch (IOException e) {log.error("fileReader关闭失败!", e);}}}
}
  • JDBCUtils
@Slf4j
public class JDBCUtils {private static String url;private static String user;private static String password;static {//读取文件,获取值try {Properties properties = PropertiesReader.getProperties("db.properties");url = properties.getProperty("url");user = properties.getProperty("user");password = properties.getProperty("password");String driver = properties.getProperty("driver");//4.注册驱动Class.forName(driver);} catch (IOException | ClassNotFoundException e) {log.error("初始化jdbc连接失败!", e);}}/*** 获取连接* @return 连接对象*/public static Connection getConnection() throws SQLException {return DriverManager.getConnection(url, user, password);}/*** 释放资源* @param rs* @param st* @param conn*/public static void close(ResultSet rs, Statement st, Connection conn) {if (rs != null) {try {rs.close();} catch (SQLException e) {e.printStackTrace();}}if (st != null) {try {st.close();} catch (SQLException e) {e.printStackTrace();}}if (conn != null) {try {conn.close();} catch (SQLException e) {e.printStackTrace();}}}
}

数据库操作类

/*** MySQL 锁操作类(加锁+释放锁)*/
@Slf4j
public class MySQLDistributedLockService {private static Connection connection;private static Statement statement;private static ResultSet resultSet;static{try {connection = JDBCUtils.getConnection();statement = connection.createStatement();resultSet = null;} catch (SQLException e) {log.error("数据库连接失败!");}}/*** 锁表 - 获取锁* @param resource      资源* @param description   锁描述* @return  是否操作成功*/public static boolean tryLock(int resource,String description){String sql = "insert into database_lock (resource,description) values (" + resource + ", '" + description + "');";//获取数据库连接try {int stat = statement.executeUpdate(sql);return stat == 1;} catch (SQLException e) {return false;}}/*** 锁表-释放锁* @return*/public static boolean releaseLock(int resource) throws SQLException {String sql = "delete from database_lock where resource = " + resource;//获取数据库连接int stat = statement.executeUpdate(sql);return stat == 1;}/*** 关闭连接*/public static void close(){log.info("当前线程: " + ManagementFactory.getRuntimeMXBean().getName().split("@")[0] +",关闭了数据库连接!");JDBCUtils.close(resultSet,statement,connection);}
}

LockTable

/*** mysql分布式锁*      执行流程: 多进程抢占数据库某个资源,然后执行业务,执行完释放资源*      锁机制: 单一进程获取锁时,则其他进程提交失败*/
@Slf4j
public class LockTable extends Thread {@Overridepublic void run() {super.run();//获取Java虚拟机的进程IDString pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];try{while(true){log.info("当前进程PID:" + pid + ",尝试获取锁资源!");if(MySQLDistributedLockService.tryLock(1,"测试锁")){log.info("当前进程PID:" + pid + ",获取锁资源成功!");//sleep模拟业务处理过程log.info("开始处理业务!");Thread.sleep(10*1000);log.info("业务处理完成!");MySQLDistributedLockService.releaseLock(1);log.info("当前进程PID: " + pid + ",释放了锁资源!");break;}else{log.info("当前进程PID: " + pid + ",获取锁资源失败!");Thread.sleep(2000);}}}catch (Exception e){log.error("抢占锁发生错误!",e);}finally {MySQLDistributedLockService.close();}}// 程序入口public static void main(String[] args) {new LockTable().start();}
}

测试
运行时开启并行执行选项,每次运行三个或三个以上进程. Allow parallel run 运行并行执行
image.png
image.png
注意事项:

  • 该锁为非阻塞的
  • 当某进程持有锁并且挂死时候会造成资源一直不释放的情况,造成死锁,因此需要维护一个定时清理任务去清理持有过久的锁
  • 要注意数据库的单点问题,最好设置备库,进一步提高可靠性
  • 该锁为非可重入锁,如果要设置成可重入锁需要添加数据库字段记录持有该锁的设备信息以及加锁次数

方式二:基于乐观锁

  • 每次执行业务前首先进行数据库查询,查询当前的需要修改的资源值(或版本号)。
  • 进行资源的修改操作,并且修改前进行资源(或版本号)的比对操作,比较此时数据库中的值是否和上一步查询结果相同。
  • 查询结果相同则修改对应资源值,不同则回到第一步。
    image.png

例子:数据库中设定某商品基本信息(名为外科口罩,数量为10),多进程对该商品进行抢购,当商品数量为0时结束抢购。
在这里插入图片描述
代码实现

    /*** 乐观锁-获取资源* @param id 资源ID* @return result*/public static ResultSet getGoodCount(int id) throws SQLException {String sql = "select  * from  database_lock_2 where id = " + id;//查询数据resultSet = statement.executeQuery(sql);return resultSet;}/*** 乐观锁-修改资源* @param id        资源ID* @param goodCount 资源* @return  修改状态*/public static boolean setGoodCount(int id, int goodCount) throws SQLException {String sql = "update database_lock_2 set good_count = good_count - 1 where id =" + id +"  and good_count = " + goodCount;int stat = statement.executeUpdate(sql);return stat == 1;}/*** 乐观锁-开启事务自动提交*/public static void AutoCommit(){try {connection.setAutoCommit(true);} catch (SQLException e) {log.error("开启自动提交!",e);}}

OptimisticLock测试类

/*** mysql分布式锁-乐观锁*  执行流程: 多进程抢购同一商品,每次抢购成功商品数量-1,商品数据量为0时退出*  锁机制: 单一进程获取锁时,则其他进程提交失败*/
@Slf4j
public class OptimisticLock extends Thread{@Overridepublic void run() {super.run();String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];ResultSet resultSet = null;String goodName = null;int goodCount = 0;try {while(true){log.info("当前线程:" + pid + ",开始抢购商品!");//获取当前商品信息resultSet = MySQLDistributedLockService.getGoodCount(1);while (resultSet.next()){goodName = resultSet.getString("good_name");goodCount = resultSet.getInt("good_count");}log.info("获取库存成功,当前商品名为:" + goodName + ",当前库存剩余量为:" + goodCount);//模拟执行业务操作Thread.sleep(2*3000);if(0 == goodCount){log.info("抢购失败,当前库存为0! ");break;}//修改库存信息,库存量-1if(MySQLDistributedLockService.setGoodCount(1,goodCount)){log.info("当前线程:" + pid + " 抢购商品:" + goodName + "成功,剩余库存为:" + (goodCount -1));//模拟延迟,防止锁每次被同一进程获取Thread.sleep(2 * 1000);}else{log.error("抢购商品:" + goodName +"失败,商品数量已被修改");}}}catch (Exception e){log.error("抢购商品发生错误!",e);}finally {if(resultSet != null){try {resultSet.close();} catch (SQLException e) {e.printStackTrace();log.error("关闭Result失败!" , e);}}MySQLDistributedLockService.close();}}public static void main(String[] args) {new OptimisticLock().start();}
}

代码测试
开启三个进程,查看执行情况
image.png
image.png
image.png
注意事项:

  • 该锁为非阻塞的
  • 该锁对于业务具有侵入式,如果设置版本号校验则增加的额外的字段,增加了数据库冗余
  • 当并发量过高时会有大量请求访问数据库的某行记录,对数据库造成很大的写压力
  • 因此乐观锁适用于并发量不高,并且写操作不频繁的场景

方式三:悲观锁实现方式(利用事务加上行/表锁)
在这里插入图片描述
实现思路

  • 关闭jdbc连接自动commit属性
  • 每次执行业务前先使用查询语句后接for update表示锁定该行数据(注意查询条件如果未命中主键或索引,此时将会从行锁变为表锁)
  • 执行业务流程修改表资源
  • 执行commit操作

代码实现
MySQLDistributedLockService

/*** 悲观锁-获取资源* @param id 资源ID* @return result*/public static ResultSet getGoodCount2(int id) throws SQLException {String sql = "select  * from  database_lock_2 where id = " + id + "for update";//查询数据resultSet = statement.executeQuery(sql);return resultSet;}/*** 悲观锁-修改资源* @param id        资源ID* @return  修改状态*/public static boolean setGoodCount2(int id) throws SQLException {String sql = "update database_lock_2 set good_count = good_count - 1 where id =" + id;int stat = statement.executeUpdate(sql);return stat == 1;}/*** 悲观锁-关闭事务自动提交*/public static void closeAutoCommit(){try {connection.setAutoCommit(false);} catch (SQLException e) {log.error("关闭自动提交失败!",e);}}/*** 悲观锁-提交事务*/public static void commit(String pid,String goodName,int goodCount) throws SQLException {connection.commit();log.info("当前线程:" + pid + "抢购商品: " + goodName + "成功,剩余库存为:" + (goodCount-1));}/*** 悲观锁-回滚*/public static void rollBack() throws SQLException {connection.rollback();}

PessimisticLock

/*** mysql 分布式锁-悲观锁*     执行流程:多个进程抢占同一个商品,执行业务完毕则通过connection.commit() 释放锁*     锁机制:单一进程获取锁时,则其他进程将阻塞等待*/
@Slf4j
public class PessimisticLock extends Thread {@Overridepublic void run() {super.run();ResultSet resultSet = null;String goodName = null;int goodCount = 0;String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];//关闭自动提交MySQLDistributedLockService.closeAutoCommit();try{while(true){log.info("当前线程:" + pid + "");//获取库存resultSet = MySQLDistributedLockService.getGoodCount2(1);while (resultSet.next()) {goodName = resultSet.getString("good_name");goodCount = resultSet.getInt("good_count");}log.info("获取库存成功,当前商品名称为:" + goodName + ",当前库存剩余量为:" + goodCount);// 模拟执行业务事件Thread.sleep(2 * 1000);if (0 == goodCount) {log.info("抢购失败,当前库存为0!");break;}// 抢购商品if (MySQLDistributedLockService.setGoodCount2(1)) {// 模拟延时,防止锁每次被同一进程获取MySQLDistributedLockService.commit(pid, goodName, goodCount);Thread.sleep(2 * 1000);} else {log.error("抢购商品:" + goodName + "失败!");}}}catch (Exception e){//抢购失败log.error("抢购商品发生错误!",e);try {MySQLDistributedLockService.rollBack();} catch (SQLException ex) {log.error("回滚失败! ",e);}}finally {if(resultSet != null){try {resultSet.close();} catch (SQLException e) {log.error("Result关闭失败!",e);}}MySQLDistributedLockService.close();}}public static void main(String[] args) {new PessimisticLock().start();}
}

测试结果
image.png
image.png
image.png
注意事项:

  • 该锁为阻塞锁
  • 每次请求存在额外加锁的开销
  • 在并发量很高的情况下会造成系统中存在大量阻塞的请求,影响系统的可用性
  • 因此悲观锁适用于并发量不高,读操作不频繁的写场景

总结:

  • 在实际使用中,由于受到性能以及稳定性约束,对于关系型数据库实现的分布式锁一般很少被用到。但是对于一些并发量不高、系统仅提供给内部人员使用的单一业务场景可以考虑使用关系型数据库分布式锁,因为其复杂度较低,可靠性也能够得到保证。

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

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

相关文章

2.4G芯片XL2408开发板,SOP16封装,芯片集成1T 8051内核单片机

XL2408开发板可用于2.4G芯片XL2408开发板的开发调试。XL2408烧录仿真需要使用WS_LINK。XL2408开发板烧录仿真需要接4根线&#xff1a;PA13:DIO&#xff0c;PA14:CLK&#xff0c;VCC&#xff0c;GND。 XL2408芯片集成射频收发机、频率收生器、晶体振荡器、调制解调器等功能模块,…

Linux【网络编程】之深入理解TCP协议

Linux【网络编程】之深入理解TCP协议 TCP协议TCP协议段格式4位首部长度---TCP报头长度信息 TCP可靠性&#xff08;确认应答&#xff09;&& 提高传输效率确认应答(ACK)机制32位序号与32为确认序号 16位窗口大小---自己接收缓冲区剩余空间的大小16位紧急指针---紧急数据处…

单元测试之- mock工具mockito

常用的mock工具mockito 在编写单元测试时&#xff0c;需要mock依赖的对象&#xff0c;减少依赖对象对测试的影响&#xff0c;Mocktio是常用的mock工具之一&#xff0c;那么mockito提供了哪些功能呢&#xff1f; Mock对象的创建和配置&#xff1a;Mockito可以通过简单的语法创建…

Spring MVC异常处理【单个控制异常处理器、全局异常处理器、自定义异常处理器】

目录 一、单个控制器异常处理 1.1 控制器方法 1.2 编写出错页面 1.3 测试结果 二、全局异常处理 2.1 一个有异常的控制器类 2.2 全局异常处理器类 2.3 测试结果 三、自定义异常处理器 3.1 自定义异常处理器 3.2 测试结果 往期专栏&文章相关导读 1. Maven系列…

git使用(由浅到深)

目录流程图 1. 分布式版本控制与集中式版本控制 1.1 集中式版本控制 集中式版本控制系统有:CVS和SVN它们的主要特点是单一的集中管理的服务器&#xff0c;保存所有文件的修订版本&#xff1b;协同开发人员通过客户端连接到这台服务器&#xff0c;取出最新的文件或者提交更新…

【CSS】3D卡片效果

效果 index.html <!DOCTYPE html> <html><head><title> Document </title><link type"text/css" rel"styleSheet" href"index.css" /></head><body><div class"card"><img…

在自定义数据集上微调Alpaca和LLaMA

本文将介绍使用LoRa在本地机器上微调Alpaca和LLaMA&#xff0c;我们将介绍在特定数据集上对Alpaca LoRa进行微调的整个过程&#xff0c;本文将涵盖数据处理、模型训练和使用流行的自然语言处理库(如Transformers和hugs Face)进行评估。此外还将介绍如何使用grado应用程序部署和…

【C++】开源:跨平台轻量日志库easyloggingpp

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍跨平台轻量日志库easyloggingpp。 无专精则不能成&#xff0c;无涉猎则不能通。。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&am…

RL — 强化学习技巧

一、说明 深度学习&#xff08;DL&#xff09;很难训练&#xff0c;强化学习&#xff08;RL&#xff09;要差得多。在早期开发中&#xff0c;遵循与 DL 相同的策略&#xff1a;保持简单&#xff01;消除任何妨碍您的花里胡哨的东西&#xff0c;并将不确定性降至最低。具体到RL&…

git clone 登录 github

git clone 登录 github 目录概述需求&#xff1a; 设计思路实现思路分析1.github 设置setting2.输入passwd 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result…

脑电信号处理与特征提取——6.运用机器学习技术和脑电进行大脑解码(涂毅恒)

目录 六、运用机器学习技术和脑电进行大脑解码 6.1 前言 6.2 基于脑电数据的机器学习基础分析 6.3 基于脑电数据的机器学习进阶分析 6.4 代码解读 六、运用机器学习技术和脑电进行大脑解码 6.1 前言 6.2 基于脑电数据的机器学习基础分析 6.3 基于脑电数据的机器学习进阶分…

C# 关于使用newlife包将webapi接口寄宿于一个控制台程序、winform程序、wpf程序运行

C# 关于使用newlife包将webapi接口寄宿于一个控制台程序、winform程序、wpf程序运行 安装newlife包 Program的Main()函数源码 using ConsoleApp3; using NewLife.Log;var server new NewLife.Http.HttpServer {Port 8080,Log XTrace.Log,SessionLog XTrace.Log }; serv…

hdu Perfect square number

题意&#xff1a; 有n个数&#xff08;n<300&#xff09;&#xff0c;将其中的任意的一个数改为x&#xff08;x在[1,300]&#xff09;&#xff0c;求改之后&#xff0c;区间和为完全平方数的最大区间个数是多少 思路&#xff1a; 将a[x]改之后的区间个数等于&#xff1a;改…

计算机和汇编语言

1.用电表示数字 我们已经学习过二进制来表示数字 二进制计数采用0和1组合表示数字 0和1很适合使用开关闭合&#xff0c;导线上有电流是1&#xff0c;无电流是 我们还可以加上小灯泡&#xff0c;来表示数 2.二进制加法机 上述这个加法机器是接受左边和下面的输入&#xff0c;把…

TCP三次握手

文章目录 目的场景TCP头部结构 目的 保证双方互相建立了连接。 场景 发生在客户端连接服务器的时候&#xff0c;当调用connect()&#xff1b;时&#xff0c;底层会通过TCP协议进行三次握手。 客户端发送 和 服务器接收客户端确定服务器可以收发&#xff0c;自己可以发送服务…

sqlyog导出mysql数据字典

1.打开sqlyog执行sql获取字典数据 SELECTt.COLUMN_NAME AS 字段名,t.COLUMN_TYPE AS 数据类型,CASE IFNULL(t.COLUMN_DEFAULT,Null) WHEN THEN 空字符串 WHEN Null THEN NULL ELSE t.COLUMN_DEFAULT END AS 默认值,CASE t.IS_NULLABLE WHEN YES THEN 是 ELSE 否 END AS 是否…

JSON动态生成表格

<!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><body><script>var fromjava"{\"total\":3,\"students\":[{\"name\":\"张三\",\&q…

Spring的创建及使用

文章目录 什么是SpringSpring项目的创建存储Bean对象读取Bean对象getBean()方法 更简单的读取和存储对象的方式路径配置使用类注解存储Bean对象关于五大类注解使用方法注解Bean存储对象Bean重命名 Bean对象的读取 使用Resource注入对象Resource VS Autowired同一类型多个bean对…

怎么让表格中的一行数据 转置 为一列数据 (WPS )

例如 我现在有一列数据 我想要 变成一行 数据 1.首先选中想要转置的数据&#xff0c;然后control C 2.接着 点击你想放置数据的位置 右键 其实 关键是 找到 选择性复制 3. 找到转置&#xff0c;勾选 最后 确定 反之亦然

PoseiSwap:基于 Nautilus Chain ,构建全新价值体系

在 DeFi Summer 后&#xff0c;以太坊自身的弊端不断凸显&#xff0c;而以 Layer2 的方式为其扩容成为了行业很长一段时间的叙事方向之一。虽然以太坊已经顺利的从 PoW 的 1.0 迈向了 PoS 的 2.0 时代&#xff0c;但以太坊创始人 Vitalik Buterin 表示&#xff0c; Layer2 未来…