分布式锁的应用场景与分布式锁实现(四):基于MySQL实现分布式锁与分布式锁总结

news2024/11/23 23:21:51

分布式锁的应用场景与分布式锁实现(三):基于Zookeeper实现分布式锁

基于MySQL实现分布式锁

​ 不管是JVM锁还是MySQL锁,为了保证线程的并发安全,都提供了悲观独占排他锁。所以独占排他也是分布式锁的基本要求。

​ 可以利用唯一键索引不能重复插入的特点表现,设计表如下:

CREATE TABLE `db_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;

基本思路

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

1606620944823

  • 1、线程同时获取锁(insert)
  • 2、获取成功,执行业务逻辑,执行完成释放锁(delete)
  • 3、其他线程等待重试

代码实现

​ 新建数据库实体类:

package tech.msop.distributed.lock.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("db_lock")
public class LockEntity {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * 锁名
     */
    private String lockName;
    /**
     * 类名
     */
    private String className;
    /**
     * 方法名
     */
    private String methodName;
    /**
     * 服务器IP
     */
    private String serverName;
    /**
     * 线程名
     */
    private String threadName;
    /**
     * 获得锁时间
     */
    private Date createTime;
    /**
     * 描述
     */
    private String desc;
}

​ 新增Mapper文件

package tech.msop.distributed.lock.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import tech.msop.distributed.lock.entity.LockEntity;

public interface LockMapper extends BaseMapper<LockEntity> {
}

​ 改造服务方法,支持MySQL分布式锁

package tech.msop.distributed.lock.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.*;
import org.apache.curator.framework.recipes.shared.SharedCount;
import org.redisson.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import tech.msop.distributed.lock.constants.StockConstant;
import tech.msop.distributed.lock.entity.LockEntity;
import tech.msop.distributed.lock.entity.StockEntity;
import tech.msop.distributed.lock.lock.DistributedLockClient;
import tech.msop.distributed.lock.lock.DistributedRedisLock;
import tech.msop.distributed.lock.mapper.LockMapper;
import tech.msop.distributed.lock.mapper.StockMapper;
import tech.msop.distributed.lock.service.IStockService;
import tech.msop.distributed.lock.zk.ZkClient;

import java.util.Date;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * 库存服务实现类 <br/>
 */
@Service
@Slf4j
public class StockServiceImpl extends ServiceImpl<StockMapper, StockEntity>
        implements IStockService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DistributedLockClient distributedLockClient;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private ZkClient zkClient;
    @Autowired
    private CuratorFramework curatorFramework;
    @Autowired
    private LockMapper lockMapper;

    /**
     * 减库存
     */
    @Override
    public void checkAndLock() {
        // 加锁
        LockEntity lock = new LockEntity();
        lock.setLockName("lock");
        lock.setClassName(this.getClass().getName());
        lock.setCreateTime(new Date());
        try {
            this.lockMapper.insert(lock);
            // 1. 查询库存信息
            String stock = redisTemplate.opsForValue().get("stock").toString();
            // 2. 判断库存是否充足
            if (stock != null && stock.length() != 0) {
                Integer st = Integer.valueOf(stock);
                if (st > 0) {
                    // 3.扣减库存
                    redisTemplate.opsForValue().set("stock", String.valueOf(--st));
                }
            }
            // 释放锁
            lockMapper.deleteById(lock.getId());
        } catch (Exception ex) {
            // 获取锁失败,重试
            try {
                Thread.sleep(50);
                this.checkAndLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

缺陷及解决方案

缺点:

  • 1、这把锁依赖数据库的可用性,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用。
    • 解决方案:给 锁 数据库搭建主备
  • 2、这把锁没有失效世家你,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再或得到锁
    • 解决方案:只要做一个定时任务,每隔一定时间就把数据库中的超时数据清理一遍
  • 3、这把锁是非重入的,同一个线程在释放锁之前无法再次获取该锁。因为数据库中数据已经存在了
    • 解决方案:记录获取锁的主机信息和线程信息,如果相同线程要获取锁,直接重入。
  • 4、受限于数据库性能,并发能力有限
    • 解决方案:无法解决

MySQL分布式锁总结

  • 独占排他互斥使用:借助唯一键索引
  • 防死锁:
    • 客户端程序获取到锁之后,客户端程序的服务器宕机。给锁记录添加一个获取锁时间列,额外的定时器检查获取锁的系统时间和当前时间的差值是否超过了阈值
    • 不可重入:通过记录服务器信息、线程信息与重入次数实现可重入性
  • 防误删:借助于ID的唯一性防止误删除
  • 原子性:一个写操作,还可以借助于MySQL的悲观锁实现
  • 可重入:通过记录服务器信息、线程信息与重入次数实现可重入性
  • 自动续期:服务器内的定时器重置获取锁的系统时间
  • 单机故障:搭建MySQL主备
  • 集群情况下锁机制失效问题。

分布式锁总结

三种方式实现分布式锁的依据:

  • Redis:基于Key的唯一性
  • Zookeeper:基于znode节点的唯一性
  • MySQL:基于唯一键索引

​ 实现复杂性或者难度角度:Zookeeper > Redis > 数据库

​ 实际性能角度:Redis > Zookeeper > 数据库

​ 可靠性角度:Zookeeper > Redis = 数据库

​ 这三种方式都不是尽善尽美,我们可以根据实际业务情况选择最适合的方案:

​ 如果追求极致性能可以选择:Redis

​ 如果追求可靠性可以选择:Zookeeper

​ 实现独占排他,对性能 对可靠性要求都不高的情况下,只是简单了解,可以选择:MySQL

常见锁分类:

  • 悲观锁: 具有强烈的独占和排他特性,在整个数据处理过程中,将数据处于锁定状态。适合于写比较多,会阻塞读操作。
  • 乐观锁: 采取了更加宽松的加锁机制,大多是基于数据版本(Version)及时间戳来实现。适合于读比较多,不会阻塞读。
  • 独占锁、互斥锁、排他锁: 保证在任一时刻,只能被一个线程独占排他持有。如Java中的synchronized、ReentrantLock
  • 共享锁: 可同时被多个线程共享持有。如CountDownLatch倒计数器、Semaphore信号量。
  • 可重入锁: 又名递归锁。同一个线程在外层方法获取锁的时候,在进入内层方法时会自动获取锁。
  • 不可重入锁: 例如早期的synchronized
  • 公平锁: 有优先级的锁,先来先得,谁先申请锁就先获取到锁。
  • 非公平锁: 无优先级的锁,后来者也机会先获取到锁。
  • 自旋锁: 当线程尝试获取锁失败时(锁已经被其他线程占用了),无限循环重试获取到锁。
  • 阻塞锁: 当线程尝试获取锁失败时,线程进入阻塞状态,直到接受信号被唤醒。在竞争激烈情况下,性能较高。
  • 读锁: 共享锁。
  • 写锁: 独占排他锁。
  • 偏向锁: 一直被一个线程所访问,那么该线程会自动获取锁。
  • 轻量级锁(CAS): 当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
  • 重量级锁: 当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋到一定次数的时候(10次),还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让他申请的线程进入阻塞,性能降低。

以上其实就是synchronized的锁升级过程

  • 表级锁: 对整张表加锁,加锁快开销小,不会出现死锁,但并发度低,会增加锁冲突的概率。
  • 行级锁: 是MySQL粒度最小的锁,只针对操作行,可大大减少锁冲突概率,并发度高,但加锁慢,开销大,会出现死锁。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在can协议的基础下编写DBC文件,然后使用该DBC文件下发can协议到底盘完整流程

目录 前言一、VectorCANdb下载及安装二、DBC文件的编写1.新建dbc文件2.建立dbc2.1根据CAN协议设置以下的signals2.2设置报文2.3建立报文与信号的关系2.4建立节点 三、编写程序使用UDP通信下发can协议1.查看can口、电脑ip以及端口号2.编写测试程序 前言 最近完成了一个项目&…

热烈庆祝兴业法拍网与宁波银行北京分行签订“法拍贷”业务合作

6月1日&#xff0c;兴业法拍网与宁波银行北京分行签订“法拍贷”合作协议。 “法拍贷”是以法院房产拍卖为核心、线上平台拓宽拍卖渠道、保险公司提供阶段性保证、公证机构加大司法效力、银行提供全程金融服务的“14”创新合作模式。该模式汇聚五方合力让更多竞拍人享受到便利…

2天时间3个面试,百度进了3面!

昨天和朋友复盘了一下最近的面试经历&#xff0c;分享给大家&#xff1a; 关于就业环境 忠告&#xff1a;如果不是在二三线买车买房结婚生子了&#xff0c;还是到一线城市去吧。 或者换个行业&#xff01; 关于焦虑和摆烂 如果你也在焦虑迷茫、精神内耗。找阳哥给你做“心理…

spring-boot版本影响Spring AOP @Before @Around @After等执行顺序

郁闷了半天&#xff0c;我通过AOP切面打印的日志顺序怪怪的&#xff0c;网上查了好几篇文章都说没问题&#xff0c;最后发现是springboot版本升级后Before Around After等执行顺序发生了变化。 1.切面类 Aspect// 这是一个切面 Component// 这是一个需要被装配的spring bean S…

零基础如何实现 Python 自动化办公 ?

996 一直是互联网老生常谈的话题了&#xff0c;但抛开其他只谈工作本身&#xff0c;你有没有想过&#xff0c;下班晚、加班&#xff0c;有时候可能是因为自己工作比较低效&#xff1f; 在这给你分享一个案例&#xff1a; 场景是在维护日活超过 3 亿用户的微博私信平台&#x…

Linux开机rc.local不自启动执行脚本其他一些问题进行补充说明

Linux开机rc.local不自启动执行脚本其他一些问题进行补充说明 在上一篇&#xff0c;我们讲了Linux开机rc.local不自启动执行脚本问题的排查思路及问题解决 这一篇我们补充一些其他的问题 问题一&#xff1a;我怎么知道我rc.local里面的命令启动成功不成功呢&#xff0c;我怎…

为什么说 Java 比 C / C++ 慢?

因为C/C允许程序员做出更多选择。 选择更多&#xff0c;那么&#xff1a; 弊端&#xff1a;开发效率难以提高&#xff0c;因为有太多选择需要斟酌。 优势&#xff1a;执行效率可以逼近极限&#xff0c;因为不会有什么抽象拦住你。 举个例子吧&#xff1a;大家可能对Java无处不…

New bing出现“重定向”无法使用解决方法来了

我又来分享干货了&#xff01;&#xff01;&#xff01; 因为要润色论文&#xff0c;又是经过几天的折腾&#xff0c;终于可以正常使用bing聊天了&#xff0c;泪目&#xff01; 首先你要有VPN才能使用New bing聊天的&#xff01;要求使用edge浏览器&#xff08;虽然我最后用的…

电脑mp3转换器哪个好用?我来告诉你电脑mp3转换器哪个好

假如你下载了一些音乐或者录音文件&#xff0c;但是它们可能不是mp3格式的&#xff0c;而是其他格式&#xff0c;如wav、flac等。而有一些设备又只能播放mp3格式的音频&#xff0c;这时候就需要使用mp3转换器将其转换成mp3格式&#xff0c;以便于在各种设备上播放和分享。不过你…

RustChinaConf 2023官网上线,精彩议题早知道

随着大会日益临近&#xff0c;我们RustChinaConf 2023筹备委员会的工作也在有条不紊的进行中。最新的好消息来了&#xff0c;官网已上线&#xff0c;访问地址&#xff1a; https://rustcc.cn/2023rustchinaconf/ 从官网进去也可以报名&#xff01; 大会时间地址 6.17 - 6.1…

电影《异形》标志性雕塑将分割为500个NFT出售

由佳士得前高管领导的美术平台Particle曾创造历史般售出班克西的《爱在空中》的10000件数字作品。 5 月 31 日 - Particle &#xff0c;作为去中心化的艺术生态系统和Banksy 的“Love is in the Air”画作的代币商&#xff0c;它将让收藏者有机会购买H.R. Giger的《异形3》原创…

我发现3个国内一直能用的免费版ChatGPT 免登免注册无广告 再不赶紧保存就没啦!

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享博主 &#x1f40b; 希望大家多多支持一下, 我们一起进步&#xff01;&#x1f604; &#x1f3c5; 如果文章对你有帮助的话&#xff0c;欢迎评论 &#x1f4ac;点赞&#x1…

Linux多Reactor多线程网络模型

多Reactor多线程网络模型是一种用于构建高性能网络应用的并发模型。它基于事件驱动的思想&#xff0c;通过使用多个Reactor线程和多个工作线程来处理并发的网络请求。 底层调用关系&#xff1a; 在多Reactor多线程网络模型中&#xff0c;通常会有一个主Reactor线程和多个工作线…

凌云出海 决胜万里丨华为云中企出海领袖班第五期顺利结束!

互联网大潮风起云涌&#xff0c;国内竞争日益激烈内卷&#xff0c;越来越多的互联网企业选择国际化走出去&#xff0c;在全球市场这个更大舞台上找寻机会。想要抓住技术红利并惠及企业全球化&#xff0c;成为当下众多出海从业者的共识。 为了帮助更多的CTO领袖具备更专业的国际…

【大模型】人工智能大模型在自动驾驶领域的应用

随着ChatGPT的火爆&#xff0c;大模型受到的关注度越来越高&#xff0c;大模型展现出的能力令人惊叹。 第一个问题&#xff1a;怎样的模型可以称之为大模型呢&#xff1f; 一般来说&#xff0c;我们认为参数量过亿的模型都可以称之为“大模型”。而在自动驾驶领域&#xff0c;大…

Hibernate框架【四】——基本映射——多对一和一对多映射

系列文章目录 Hibernate框架【三】——基本映射——一对一映射 基本映射——多对一和一对多映射 系列文章目录前言一、多对一映射是什么&#xff1f;1.案例&#xff1a;现在有两个实体User实体和Group&#xff0c;其中多个User属于一个Group&#xff0c;表现出多对一的关系。①…

图解LeetCode——98. 验证二叉搜索树

一、题目 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。 二、示例 2.1>…

扫描出项目中未被引用的控制器接口的工具类(超级好用)

自己的项目随着不断开发迭代&#xff0c;越来越庞大&#xff0c;后台的接口也就越来越多&#xff0c;难免会有一些接口后来并没有使用到&#xff0c;但是还在代码中&#xff0c;权限管理的时候这部分接口是非常多余的&#xff0c;为了避免这个问题&#xff0c;本章提供了一个工…

回归测试最小化(贪心算法,帕累托支配)

回归测试最小化(贪心算法,帕累托支配) 介绍 有时我们不能只是重新运行我们的测试&#xff08;例如&#xff0c;当我们 换界面&#xff09;。 回归测试可能很昂贵: (1)一些公司通宵运行回归测试套件。 (2) 对于嵌入式系统&#xff0c;我们可能必须测试正在使用的软件&#xff0…

党校学员毕业自我鉴定总结样文分享

党校学员毕业自我鉴定总结样文分享1 岁月匆匆似流水&#xff0c;美好的研究生三年时光马上就快结束了。经过良师的悉心指导以及自己的努力奋力拚搏、自强不息&#xff0c;我渐渐的成为了一个能适应现代社会要求的硕士毕业生&#xff0c;并为做一个知识型的社会主义建设者打下坚…