4、基于mysql实现分布式锁

目录

    • [4.1. 基本思路](#4.1. 基本思路)
    • [4.2. 代码实现](#4.2. 代码实现)
    • [4.3 缺陷及解决方案](#4.3 缺陷及解决方案)

4.1. 基本思路

synchronized关键字和ReetrantLock锁都是独占排他锁,即多个线程争抢一个资源时,同一时刻只有一个线程可以抢占该资源,其他线程只能阻塞等待,直到占有资源的线程释放该资源

利用唯一键索引不能重复插入的特点实现

4.2. 代码实现

1、创建锁表,和对应的实体类

java 复制代码
CREATE TABLE `tb_lock` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `lock_name` varchar(50) NOT NULL COMMENT '锁名',
  `class_name` varchar(100) DEFAULT NULL COMMENT '类名',
  `method_name` varchar(50) DEFAULT NULL COMMENT '方法名',
  `server_name` varchar(50) DEFAULT NULL COMMENT '服务器ip',
  `thread_name` varchar(50) DEFAULT NULL COMMENT '线程名',
  `create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '获取锁时间',
  `desc` varchar(100) DEFAULT NULL COMMENT '描述',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_unique` (`lock_name`)
) ENGINE=InnoDB AUTO_INCREMENT=1332899824461455363 DEFAULT CHARSET=utf8;

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tb_lock")
public class Lock {

    private Long id;
    private String lockName;
    private String className;
    private String methodName;
    private String serverName;
    private String threadName;
    private Date createTime;
    private String desc;
}

LockMapper接口:

java 复制代码
public interface LockMapper extends BaseMapper<Lock> {
}

改造StockService:

java 复制代码
@Service
public class StockService {

    @Autowired
    private StockMapper stockMapper;

    @Autowired
    private LockMapper lockMapper;

    /**
     * 数据库分布式锁
     */
    public void checkAndLock() {

        // 加锁
        Lock lock = new Lock(null, "lock", this.getClass().getName(), new Date(), null);
        try {
            this.lockMapper.insert(lock);
             // 先查询库存是否充足
	        Stock stock = this.stockMapper.selectById(1L);
	
	        // 再减库存
	        if (stock != null && stock.getCount() > 0){
	
	            stock.setCount(stock.getCount() - 1);
	            this.stockMapper.updateById(stock);
	        }
        } catch (Exception ex) {
            // 获取锁失败,则重试
            try {
                Thread.sleep(50);
                this.checkAndLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
	         // 释放锁
	        this.lockMapper.deleteById(lock.getId());
        }  
    }
}

测试:

使用Jmeter压力测试结果:

查看redis 库存为0,表明mysql锁确实起到作用了

4.3 缺陷及解决方案

上述mysql实现分布式锁的过程中还存在一些问题!!

1、防止死锁

客户端程序获取到锁之后,客户端程序宕机的话,会导致持有的锁不会释放(死锁)

解决方案:给锁记录添加一个释放锁的时间戳,启一个定时任务清理过期的锁记录

2、防止误删

有锁记录是唯一的,mysql作为分布式锁不存在误删问题

3、可重入

记录获取锁的主机信息和线程信息,如果相同线程要获取锁,直接重入。

4、锁的自动续期

服务器内的定时器自动重置锁的过期时间

5、单机故障

搭建mysql主备

6、集群情况下锁失效问题

当一个线程获取到锁之后,此时主节点宕机,并且mysql主节点的数据还没有同步到从节点。又一个线程尝试去获取锁吗,那么将会获取锁成功

相关推荐
ACP广源盛139246256732 小时前
国产算力互联IX8024@ACP#Kimi K3 开源后的端侧部署硬件架构分析
大数据·人工智能·分布式·单片机·嵌入式硬件
花生了什么事o3 小时前
分布式 ID 生成方案:从数据库自增到雪花算法
数据库·分布式·算法
爱吃提升18 小时前
python 分布式爬虫、爬虫合规与综合实战项目
分布式·爬虫·python
ZENERGY-众壹21 小时前
跨品牌逆变器升级:华为锦浪德业 API 对接的 7 个坑
分布式·华为·数据归一化·光伏运维·逆变器api
薛定谔的猫19821 天前
LLaMA-Factory +DeepSpeed 分布式多卡训练实战全解
分布式·deepspeed·zero·多卡训练·多卡分布式训练
稚南城才子,乌衣巷风流1 天前
RabbitMQ 消息队列:从入门到实战
分布式·rabbitmq
Database_Cool_2 天前
单机 MySQL 迁移到分布式数据库方便吗?阿里云 PolarDB-X 100% MySQL 协议兼容零改造平滑迁移
数据库·分布式·mysql
肥胖小羊2 天前
解决企业微信 AccessToken 刷新并发冲突的分布式锁机制实践
分布式·企业微信
风中凌乱3 天前
kafka新版本集群的安装与部署
分布式·kafka
SLD_Allen3 天前
大规模分布式AI训练基础设施
人工智能·分布式·模型训练