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

打印 上一主题 下一主题

主题 1118|帖子 1118|积分 3354

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
目录

前言(背景-思绪):
1.新增锁锁表
2.详细代码实现
2.1 工具方法
2.2 mapper实现
2.3 测试小列子
2.4 极度情况服务挂了,数据存锁一直得不到释放


前言(背景-思绪):

我们组redis翻车了,给客户带来问题。固然紧急出包得到相识决,但是大领导一句话,直接禁用了。下面是我基于mysql(oracle)数据库实现的分布式锁:
主题思绪就是,借助数据库的行锁,然后拿到锁则继续执行,拿到锁并启动ScheduledExecutorService (定时周期执行任务-每隔锁过期时间/3续期一次,雷同看门狗机制),拿不到锁则自旋等待。
废话不多说上代码-拿去即可使用:
实现背景:springboot、mybatis、mybatis-plus
1.新增锁锁表

  1. CREATE TABLE `cache_lock_info` (
  2.   `lock_name` varchar(255) NOT NULL,
  3.   `lock_value` varchar(255) NOT NULL,
  4.   `expire_date_time` datetime NOT NULL,
  5.   `update_date` date NOT NULL,
  6.   `update_time` time NOT NULL,
  7.   PRIMARY KEY (`lock_name`)
  8. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
复制代码
2.详细代码实现

2.1 工具方法

  1. package onlyqi.dayday01lock.lock;
  2. import onlyqi.dayday01lock.lock.mapper.LockInfoMapper;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.stereotype.Service;
  5. import javax.annotation.PreDestroy;
  6. import java.util.Date;
  7. import java.util.concurrent.Executors;
  8. import java.util.concurrent.ScheduledExecutorService;
  9. import java.util.concurrent.TimeUnit;
  10. @Service
  11. public class DistributedLockService {
  12.     @Autowired
  13.     private LockInfoMapper lockInfoMapper;
  14.     private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(10);
  15.     /**
  16.      * 尝试获取锁并启动续期任务
  17.      *
  18.      * @param lockName  锁名称
  19.      * @param lockValue 锁值(唯一标识)
  20.      * @param expireSec 锁过期时间(秒)
  21.      * @return 是否获取成功
  22.      */
  23.     public boolean tryLockWithRenewal(String lockName, String lockValue, int expireSec) {
  24.         Date now = new Date();
  25.         Date expireDateTime = new Date(now.getTime() + expireSec * 1000L);
  26.         try {
  27.             lockInfoMapper.insertLock(lockName, lockValue, expireDateTime, now, now);
  28.             // 检查线程池是否已关闭,如果已关闭则重新初始化
  29.             if (scheduler.isShutdown()) {
  30.                 scheduler = Executors.newScheduledThreadPool(1);
  31.             }
  32.             // 启动续期任务
  33.             scheduler.scheduleAtFixedRate(() -> {
  34.                 if (!renewLock(lockName, lockValue, expireSec)) {
  35.                     // 续期失败,停止任务
  36.                     scheduler.shutdown();
  37.                 }
  38.             }, expireSec / 3, expireSec / 3, TimeUnit.SECONDS);
  39.             return true;
  40.         } catch (Exception e) {
  41.             // ===========防止服务挂了,数据存在过期锁一直得不到释放====================//
  42.             // 锁已过期,删除旧记录并重新获取锁=======然后返回false,下次自旋获取====此方式不太推荐,推荐服务挂了,再次部署的时候删除数据库所有过期的key。此方法仅做参考//
  43.            lockInfoMapper.deleteLockExpire(lockName,expireDateTime);
  44.             // 主键冲突,锁已被其他线程持有
  45.             return false;
  46.         }
  47.     }
  48.     /**
  49.      * 阻塞自旋获取锁
  50.      *
  51.      * @param lockName   锁名称
  52.      * @param lockValue  锁值(唯一标识)
  53.      * @param expireSec  锁过期时间(秒)
  54.      * @param timeoutSec 自旋超时时间(秒)
  55.      * @return 是否获取成功
  56.      */
  57.     public boolean spinLock(String lockName, String lockValue, int expireSec, int timeoutSec) {
  58.         long startTime = System.currentTimeMillis();
  59.         while (System.currentTimeMillis() - startTime < timeoutSec * 1000L) {
  60.             if (tryLockWithRenewal(lockName, lockValue, expireSec)) {
  61.                 // 获取锁成功
  62.                 return true;
  63.             }
  64.             try {
  65.                 Thread.sleep(100); // 自旋间隔
  66.             } catch (InterruptedException e) {
  67.                 Thread.currentThread().interrupt();
  68.                 return false;
  69.             }
  70.         }
  71.         // 超时未获取锁
  72.         return false;
  73.     }
  74.     /**
  75.      * 续期锁
  76.      *
  77.      * @param lockName  锁名称
  78.      * @param lockValue 锁值(唯一标识)
  79.      * @param expireSec 续期时间(秒)
  80.      * @return 是否续期成功
  81.      */
  82.     public boolean renewLock(String lockName, String lockValue, int expireSec) {
  83.         Date now = new Date();
  84.         Date expireDateTime = new Date(now.getTime() + expireSec * 1000L);
  85. //        LockInfo lockInfo = lockInfoMapper.selectById(lockName);
  86. //        lockInfo.setExpireDateTime(expireDateTime);
  87. //        int rowsUpdated = lockInfoMapper.updateById(lockInfo);
  88.          int rowsUpdated = lockInfoMapper.renewLock(lockName, lockValue, expireDateTime, now, now);
  89.         return rowsUpdated > 0; // 更新成功表示续期成功
  90.     }
  91.     /**
  92.      * 释放锁并停止续期任务
  93.      *
  94.      * @param lockName  锁名称
  95.      * @param lockValue 锁值(唯一标识)
  96.      * @return 是否释放成功
  97.      */
  98.     public boolean releaseLockWithRenewal(String lockName, String lockValue) {
  99.         scheduler.shutdown(); // 停止续期任务
  100.         return releaseLock(lockName, lockValue);
  101.     }
  102.     /**
  103.      * 释放锁
  104.      *
  105.      * @param lockName  锁名称
  106.      * @param lockValue 锁值(唯一标识)
  107.      * @return 是否释放成功
  108.      */
  109.     public boolean releaseLock(String lockName, String lockValue) {
  110.         int rowsDeleted = lockInfoMapper.deleteLock(lockName, lockValue);
  111.         return rowsDeleted > 0; // 删除成功表示释放成功
  112.     }
  113.     /**
  114.      * 销毁时关闭线程池
  115.      */
  116.     @PreDestroy
  117.     public void destroy() {
  118.         if (scheduler != null) {
  119.             scheduler.shutdown();
  120.         }
  121.     }
  122. }
复制代码
2.2 mapper实现

  1. package onlyqi.dayday01lock.lock.mapper;
  2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  3. import onlyqi.dayday01lock.lock.LockInfo;
  4. import org.apache.ibatis.annotations.Mapper;
  5. import org.apache.ibatis.annotations.Param;
  6. import java.util.Date;
  7. @Mapper
  8. public interface LockInfoMapper extends BaseMapper<LockInfo> {
  9.     int insertLock(@Param("lockName") String lockName, @Param("lockValue") String lockValue,
  10.                    @Param("expireDateTime") Date expireDateTime, @Param("updateDate") Date updateDate,
  11.                    @Param("updateTime") Date updateTime);
  12.     int renewLock(@Param("lockName") String lockName, @Param("lockValue") String lockValue,
  13.                   @Param("expireDateTime") Date expireDateTime, @Param("updateDate") Date updateDate,
  14.                   @Param("updateTime") Date updateTime);
  15.     int deleteLock(@Param("lockName") String lockName, @Param("lockValue") String lockValue);
  16.     int deleteLockExpire(@Param("lockName") String lockName, @Param("expireDateTime") Date expireDateTime);
  17. }
复制代码
2.3 测试小列子

  1. package onlyqi.dayday01lock.lock;
  2. import onlyqi.dayday01lock.lock.mapper.LockInfoMapper;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.stereotype.Component;
  5. @Component
  6. public class DistributedLockExample {
  7.     @Autowired
  8.     private DistributedLockService distributedLockService;
  9.     @Autowired
  10.     private LockInfoMapper lockInfoMapper;
  11.     public void lockTest(String operatorNo) {
  12.         String lockName = "my_lock";
  13.         String lockValue = Thread.currentThread().getName();
  14.         int expireSec = 30; // 锁过期时间为 30 秒
  15.         try {
  16.             // 尝试获取锁并启动续期任务
  17.             if (distributedLockService.spinLock(lockName, lockValue, expireSec,60)) {
  18.                 System.out.println("Lock acquired successfully!======operatorNo:"+operatorNo+"========在执行");
  19.                 // 模拟业务逻辑-模拟业务逻辑执行3 秒
  20.                 Thread.sleep(3000);
  21.                 System.out.println("Business logic completed!==="+operatorNo+"===执行结束=====");
  22.             } else {
  23.                 System.out.println("Failed to acquire lock!"+operatorNo);
  24.             }
  25.         } catch (InterruptedException e) {
  26.             e.printStackTrace();
  27.         } finally {
  28.             // 释放锁并停止续期任务
  29.             if (distributedLockService.releaseLockWithRenewal(lockName, lockValue)) {
  30.                 System.out.println("Lock released successfully!");
  31.             }
  32.         }
  33.     }
  34. }
复制代码
2.4 极度情况服务挂了,数据存锁一直得不到释放

实现一:每次获取不到锁的时候,增长删除数据库过期的锁的动作
实现二:启动项目的时候,扫除数据库所有过期的锁(推荐)
  1. @Component
  2. public class StartupTask {
  3.     @PostConstruct
  4.     public void init() {
  5.         // 在应用启动时执行的逻辑
  6.       Date now = new Date();
  7.         Date nowDateTime = new Date(now.getTime());
  8.         deleteExpirekdKey(nowDateTime )
  9.     }
  10. }
复制代码
下一篇写基于mysql数据库实现缓存
完备demo代码已上传github:https://github.com/qi-only/daydayuo-go


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

笑看天下无敌手

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表