iis服务器助手广告广告
返回顶部
首页 > 资讯 > 数据库 >MySQL做分布式锁
  • 785
分享到

MySQL做分布式锁

mysql分布式数据库 2023-09-15 11:09:40 785人浏览 八月长安
摘要

分布式锁Mysql实现方式 方式1:唯一索引 创建锁表,内部存在字段表示资源名及资源描述,同一资源名使用数据库唯一性限制。多个进程同时往数据库锁表中写入对某个资源的占有记录,当某个进程成功写入时则表示

分布式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.Driverurl=jdbc:mysql://localhost:3306/distribute_lock?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghaiuser=rootpassWord=123456
  • PropertiesReader
@Slf4jpublic 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
@Slf4jpublic 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);        }    }        public static Connection getConnection() throws SQLException {        return DriverManager.getConnection(url, user, password);    }        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();            }        }    }}

数据库操作类

@Slf4jpublic 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("数据库连接失败!");        }    }        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;        }    }        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

@Slf4jpublic class LockTable extends Thread {    @Override    public void run() {        super.run();        //获取Java虚拟机的进程ID        String 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时结束抢购。
在这里插入图片描述
代码实现

        public static ResultSet getGoodCount(int id) throws SQLException {        String sql = "select  * from  database_lock_2 where id = " + id;        //查询数据        resultSet = statement.executeQuery(sql);        return resultSet;    }        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测试类

@Slf4jpublic class OptimisticLock extends Thread{    @Override    public 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;                }                //修改库存信息,库存量-1                if(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

        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;    }        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

@Slf4jpublic class PessimisticLock extends Thread {    @Override    public 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
注意事项:

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

总结:

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

来源地址:https://blog.csdn.net/Forbidden_City/article/details/132052106

您可能感兴趣的文档:

--结束END--

本文标题: MySQL做分布式锁

本文链接: https://www.lsjlt.com/news/408564.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • MySQL做分布式锁
    分布式锁mysql实现方式 方式1:唯一索引 创建锁表,内部存在字段表示资源名及资源描述,同一资源名使用数据库唯一性限制。多个进程同时往数据库锁表中写入对某个资源的占有记录,当某个进程成功写入时则表示...
    99+
    2023-09-15
    mysql 分布式 数据库
  • MySQL实现分布式锁
    目录基于MySQL分布式锁实现原理及代码MySQL锁InnoDB共享锁排它锁MyISAM表共享读锁表独占写锁分布式锁实现难点:为什么需要for(;总结基于MySQL分布式锁实现原理及...
    99+
    2022-11-13
    MySQL实现分布式锁 MySQL分布式锁
  • 【分布式】分布式锁
    目录 一、分布式锁介绍二、基于 Redis 实现分布式锁1. 如何基于 Redis 实现一个最简易的分布式锁?2.为什么要给锁设置一个过期时间?3. 如何实现锁的优雅续期?4. 如何实现可重入锁? 一、分布式锁介绍 单机多线...
    99+
    2023-08-17
    分布式 java jvm
  • Redisson分布式锁
    文章目录 一、Redisson简单介绍二、Redisson简单使用1. maven引用2. RedisConfig配置3. StockRedissonService4. 测试 三、Redisson源码1. 加锁2. 解锁3. 自...
    99+
    2023-08-19
    分布式 redis java 缓存
  • redis分布式锁与zk分布式锁的对比分析
    目录Redis实现分布式锁原理能实现的锁类型注意事项 zk实现分布式锁原理能实现的锁类型两种锁的对比在分布式环境下,传统的jvm级别的锁会失效,那么分布式锁就是非常有必要的一个技术,一般我们可以通过redis,...
    99+
    2022-11-18
    redis分布式锁 分布式锁 zk分布式锁
  • mysql中怎么实现分布式锁
    这篇文章主要介绍“mysql中怎么实现分布式锁”,在日常操作中,相信很多人在mysql中怎么实现分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”mysql中怎么实现分布式锁”的疑惑有所帮助!接下来,请跟...
    99+
    2023-06-27
  • 分布式锁的原理及Redis怎么实现分布式锁
    这篇文章主要介绍“分布式锁的原理及Redis怎么实现分布式锁”,在日常操作中,相信很多人在分布式锁的原理及Redis怎么实现分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解...
    99+
    2023-02-02
    redis
  • DBA_2PC_PENDING中的分布式锁-解锁
     运行shell脚本后,会生成 roll.sql文件。...
    99+
    2023-06-06
  • 分别使用Redis、MySQL、ZooKeeper构建分布式锁
    文章目录 前言一、分布式锁简介二、分布式锁要求三、实现方案四、数据库分布式锁1、悲观锁2、乐观锁 五、Zookeeper分布式锁1、引入Curator和ZooKeeper2、配置ZooKe...
    99+
    2023-09-16
    分布式 redis mysql
  • Redis——》实现分布式锁
    推荐链接:     总结——》【Java】     总结——》【Mysql】     总结——》【Redis】     总结——》【Kafka】     总结——》【Spring】     总结—...
    99+
    2023-09-03
    redis 分布式 过期 lua
  • Redis实现分布式锁
    单体锁存在的问题 在单体应用中,如果我们对共享数据不进行加锁操作,多线程操作共享数据时会出现数据一致性问题。 (下述实例是一个简单的下单问题:从redis中获取库存,检查库存是否够,>0才允许下单) 我们的解决办法通常是加锁。如下加单体锁...
    99+
    2023-08-16
    分布式 java jvm
  • 深入理解:分布式之抉择分布式锁
    前言:目前网上大部分的基于zookpeer,和redis的分布式锁的文章都不够全面。要么就是特意避开集群的情况,要么就是考虑不全,读者看着还是一脸迷茫。坦白说,这种老题材,很难写出新创意,博主内心战战兢兢,如履薄冰,文中有什么不严谨之处,欢...
    99+
    2023-06-05
  • Redisson分布式锁之加解锁源码分析
    这篇文章主要介绍“Redisson分布式锁之加解锁源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Redisson分布式锁之加解锁源码分析”文章能帮助大家解决问题。锁的可重入性我们都知道,Ja...
    99+
    2023-07-05
  • mysql分布式锁实现的方法是什么
    MySQL本身并没有提供分布式锁的实现方法,但可以借助MySQL的特性和其他技术来实现分布式锁。以下是几种常见的实现方法:1. 基于...
    99+
    2023-10-09
    mysql
  • Redisson分布式锁之加解锁详解
    目录引言锁的可重入性加锁锁续命释放锁引言 2023的金三银四来的没想象中那么激烈,一个朋友前段时间投了几十家,多数石沉大海,好不容易等来面试机会,就恰好被问道项目中关于分布式锁的应用,后涉及Redisson实现分布式锁...
    99+
    2023-03-19
    Redisson分布式加解锁 Redisson加解锁
  • Redis Template实现分布式锁
    今天就跟大家聊聊有关Redis Template实现分布式锁,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。可靠性首先,为了确保分布式锁可用,我们至少...
    99+
    2024-04-02
  • 如何使用分布式锁
    本篇内容主要讲解“如何使用分布式锁”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何使用分布式锁”吧!什么是分布式锁分布式锁又可以解决哪些问题呢在我们的系统还没...
    99+
    2024-04-02
  • Redis分布式锁有哪些
    Redis分布式锁有哪些?这个问题可能是我们日常学习或工作经常见到的。希望通过这个问题能让你收获颇深。下面是小编给大家带来的参考内容,让我们一起来看看吧!我们通常使用的synchronized或者Lock都...
    99+
    2024-04-02
  • python实现redis分布式锁
    #!/usr/bin/env python # coding=utf-8 import time import redis class RedisLock(object): def __init__(self, key): ...
    99+
    2023-01-31
    分布式 python redis
  • Redis实现分布式锁(SETNX)
    目录 1、什么是分布式锁 2、分布式锁应具备的条件         3、为什么使用分布式锁 4、SETNX介绍 5、分布式锁实现 6、效果演示 7、Redisson分布式锁详解 8、Lua脚本实现可重入分布式锁 1、什么是分布式锁  ...
    99+
    2023-10-21
    redis 分布式 java spring boot 后端
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作