当前位置: 首页 > article >正文

基于mysql数据库实现分布式锁

目录

前言(背景-思路):

1.新增锁锁表

2.具体代码实现

2.1 工具方法

2.2 mapper实现

2.3 测试小列子

2.4 极端情况服务挂了,数据存锁一直得不到释放


前言(背景-思路):

我们组redis翻车了,给客户带来问题。虽然紧急出包得到了解决,但是大领导一句话,直接禁用了。下面是我基于mysql(oracle)数据库实现的分布式锁:

主题思路就是,借助数据库的行锁,然后拿到锁则继续执行,拿到锁并启动ScheduledExecutorService (定时周期执行任务-每隔锁过期时间/3续期一次,类似看门狗机制),拿不到锁则自旋等待。

废话不多说上代码-拿去即可使用:

实现背景:springboot、mybatis、mybatis-plus

1.新增锁锁表

CREATE TABLE `cache_lock_info` (
  `lock_name` varchar(255) NOT NULL,
  `lock_value` varchar(255) NOT NULL,
  `expire_date_time` datetime NOT NULL,
  `update_date` date NOT NULL,
  `update_time` time NOT NULL,
  PRIMARY KEY (`lock_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

2.具体代码实现

2.1 工具方法
package onlyqi.dayday01lock.lock;


import onlyqi.dayday01lock.lock.mapper.LockInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PreDestroy;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Service
public class DistributedLockService {

    @Autowired
    private LockInfoMapper lockInfoMapper;

    private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(10);

    /**
     * 尝试获取锁并启动续期任务
     *
     * @param lockName  锁名称
     * @param lockValue 锁值(唯一标识)
     * @param expireSec 锁过期时间(秒)
     * @return 是否获取成功
     */
    public boolean tryLockWithRenewal(String lockName, String lockValue, int expireSec) {
        Date now = new Date();
        Date expireDateTime = new Date(now.getTime() + expireSec * 1000L);
        try {
            lockInfoMapper.insertLock(lockName, lockValue, expireDateTime, now, now);
            // 检查线程池是否已关闭,如果已关闭则重新初始化
            if (scheduler.isShutdown()) {
                scheduler = Executors.newScheduledThreadPool(1);
            }
            // 启动续期任务
            scheduler.scheduleAtFixedRate(() -> {
                if (!renewLock(lockName, lockValue, expireSec)) {
                    // 续期失败,停止任务
                    scheduler.shutdown();
                }
            }, expireSec / 3, expireSec / 3, TimeUnit.SECONDS);
            return true;
        } catch (Exception e) {
            // ===========防止服务挂了,数据存在过期锁一直得不到释放====================//
            // 锁已过期,删除旧记录并重新获取锁=======然后返回false,下次自旋获取====此方式不太推荐,推荐服务挂了,再次部署的时候删除数据库所有过期的key。此方法仅做参考//
           lockInfoMapper.deleteLockExpire(lockName,expireDateTime);
            // 主键冲突,锁已被其他线程持有
            return false;
        }
    }

    /**
     * 阻塞自旋获取锁
     *
     * @param lockName   锁名称
     * @param lockValue  锁值(唯一标识)
     * @param expireSec  锁过期时间(秒)
     * @param timeoutSec 自旋超时时间(秒)
     * @return 是否获取成功
     */
    public boolean spinLock(String lockName, String lockValue, int expireSec, int timeoutSec) {
        long startTime = System.currentTimeMillis();
        while (System.currentTimeMillis() - startTime < timeoutSec * 1000L) {
            if (tryLockWithRenewal(lockName, lockValue, expireSec)) {
                // 获取锁成功
                return true;
            }
            try {
                Thread.sleep(100); // 自旋间隔
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
        // 超时未获取锁
        return false;
    }

    /**
     * 续期锁
     *
     * @param lockName  锁名称
     * @param lockValue 锁值(唯一标识)
     * @param expireSec 续期时间(秒)
     * @return 是否续期成功
     */
    public boolean renewLock(String lockName, String lockValue, int expireSec) {
        Date now = new Date();
        Date expireDateTime = new Date(now.getTime() + expireSec * 1000L);
//        LockInfo lockInfo = lockInfoMapper.selectById(lockName);
//        lockInfo.setExpireDateTime(expireDateTime);
//        int rowsUpdated = lockInfoMapper.updateById(lockInfo);
         int rowsUpdated = lockInfoMapper.renewLock(lockName, lockValue, expireDateTime, now, now);
        return rowsUpdated > 0; // 更新成功表示续期成功
    }

    /**
     * 释放锁并停止续期任务
     *
     * @param lockName  锁名称
     * @param lockValue 锁值(唯一标识)
     * @return 是否释放成功
     */
    public boolean releaseLockWithRenewal(String lockName, String lockValue) {
        scheduler.shutdown(); // 停止续期任务
        return releaseLock(lockName, lockValue);
    }

    /**
     * 释放锁
     *
     * @param lockName  锁名称
     * @param lockValue 锁值(唯一标识)
     * @return 是否释放成功
     */
    public boolean releaseLock(String lockName, String lockValue) {
        int rowsDeleted = lockInfoMapper.deleteLock(lockName, lockValue);
        return rowsDeleted > 0; // 删除成功表示释放成功
    }

    /**
     * 销毁时关闭线程池
     */
    @PreDestroy
    public void destroy() {
        if (scheduler != null) {
            scheduler.shutdown();
        }
    }
}

2.2 mapper实现
package onlyqi.dayday01lock.lock.mapper;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import onlyqi.dayday01lock.lock.LockInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.Date;

@Mapper
public interface LockInfoMapper extends BaseMapper<LockInfo> {
    int insertLock(@Param("lockName") String lockName, @Param("lockValue") String lockValue,
                   @Param("expireDateTime") Date expireDateTime, @Param("updateDate") Date updateDate,
                   @Param("updateTime") Date updateTime);

    int renewLock(@Param("lockName") String lockName, @Param("lockValue") String lockValue,
                  @Param("expireDateTime") Date expireDateTime, @Param("updateDate") Date updateDate,
                  @Param("updateTime") Date updateTime);

    int deleteLock(@Param("lockName") String lockName, @Param("lockValue") String lockValue);

    int deleteLockExpire(@Param("lockName") String lockName, @Param("expireDateTime") Date expireDateTime);
}
2.3 测试小列子
package onlyqi.dayday01lock.lock;

import onlyqi.dayday01lock.lock.mapper.LockInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class DistributedLockExample {

    @Autowired
    private DistributedLockService distributedLockService;
    @Autowired
    private LockInfoMapper lockInfoMapper;

    public void lockTest(String operatorNo) {
        String lockName = "my_lock";
        String lockValue = Thread.currentThread().getName();
        int expireSec = 30; // 锁过期时间为 30 秒
        try {
            // 尝试获取锁并启动续期任务
            if (distributedLockService.spinLock(lockName, lockValue, expireSec,60)) {
                System.out.println("Lock acquired successfully!======operatorNo:"+operatorNo+"========在执行");
                // 模拟业务逻辑-模拟业务逻辑执行3 秒
                Thread.sleep(3000);
                System.out.println("Business logic completed!==="+operatorNo+"===执行结束=====");
            } else {
                System.out.println("Failed to acquire lock!"+operatorNo);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 释放锁并停止续期任务
            if (distributedLockService.releaseLockWithRenewal(lockName, lockValue)) {
                System.out.println("Lock released successfully!");
            }
        }
    }
}
2.4 极端情况服务挂了,数据存锁一直得不到释放

实现一:每次获取不到锁的时候,增加删除数据库过期的锁的动作

实现二:启动项目的时候,清除数据库所有过期的锁(推荐)

@Component
public class StartupTask {

    @PostConstruct
    public void init() {
        // 在应用启动时执行的逻辑
      Date now = new Date();
        Date nowDateTime = new Date(now.getTime());
        deleteExpirekdKey(nowDateTime )
    }
}

下一篇写基于mysql数据库实现缓存

完整demo代码已上传github:https://github.com/qi-only/daydayuo-go


http://www.kler.cn/a/467799.html

相关文章:

  • 从configure.ac到构建环境:解析Mellanox OFED内核模块构建脚本
  • ESP32-C3 AT WiFi AP 启 TCP Server 被动接收模式 + BLE 共存
  • 【前端知识】手搓微信小程序
  • Redis - 5 ( 18000 字 Redis 入门级教程 )
  • 华为 Sensor 省电策略调研
  • Http基础认证摘要认证
  • 73 mysql replication 集群的交互
  • Python 数据结构揭秘:栈与队列
  • HDFS块预留导致的存储空间异常的问题探究
  • python.exe无法找到程序入口 无法定位程序输入点(Anaconda Prompt报错)
  • 基于JAVA+SpringBoot+Vue的校园外卖服务系统
  • 无刷电机驱动板原理图解析
  • LinuxC高级day2
  • 模型训练二三事:参数个数、小批量、学习率衰减、输入形状
  • 044_Standalone App in Matlab中发布独立应用
  • [网络安全]sqli-labs Less-3 解题详析
  • vue elementUI Plus实现拖拽流程图,不引入插件,纯手写实现。
  • Vue的data和methods
  • 面试题解,Java中的“字节码”剖析
  • HP 电脑开机黑屏 | 故障判断 | BIOS 恢复 | BIOS 升级
  • 改善 Kibana 中的 ES|QL 编辑器体验
  • 智能工厂的设计软件 应用场景的一个例子: 为AI聊天工具添加一个知识系统 之20 再次重建 之5 项目文件三大部 整“拼”项目文档总述
  • vs 2022 中xml 粘贴为Class 中,序列化出来的xml 的使用
  • 九进制转10进制
  • Git 如何在IDEA中进行使用
  • SAP系统中的标准价、移动平均价是什么?有何区别?物料分类账的优点