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

MySql如何实现分布式锁

本篇我们使用mysql实现一个分布式锁。
环境:mysql8,navicat,maven,springboot2.3.11,mybatis-plus

分布式锁的功能

1,分布式锁使用者位于不同的机器中,锁获取成功之后,才可以对共享资源进行操作

2,锁具有重入的功能:即一个使用者可以多次获取某个锁

3,获取锁有超时的功能:即在指定的时间内去尝试获取锁,超过了超时时间,如果还未获取成功,则返回获取失败

4,能够自动容错,比如:A机器获取锁lock1之后,在释放锁lock1之前,A机器挂了,导致锁lock1未释放,结果会lock1一直被A机器占有着,遇到这种情况时,分布式锁要能够自动解决,可以这么做:持有锁的时候可以加个持有超时时间,超过了这个时间还未释放的,其他机器将有机会获取锁

预备技能:乐观锁

通常我们修改表中一条数据过程如下:

t1:select获取记录R1
t2:对R1进行编辑
t3:update R1

我们来看一下上面的过程存在的问题:

如果A、B两个线程同时执行到t1,他们俩看到的R1的数据一样,然后都对R1进行编辑,然后去执行t3,最终2个线程都会更新成功,后面一个线程会把前面一个线程update的结果给覆盖掉,这就是并发修改数据存在的问题。

我们可以在表中新增一个版本号,每次更新数据时候将版本号作为条件,并且每次更新时候版本号+1,过程优化一下,如下:

t1:打开事务start transaction
t2:select获取记录R1,声明变量v=R1.version
t3:对R1进行编辑
t4:执行更新操作update R1 set version = version + 1 where user_id=#user_id# and version = #v#;
t5:t4中的update会返回影响的行数,我们将其记录在count中,然后根据count来判断提交还是回滚if(count==1){//提交事务commit;}else{//回滚事务rollback;}

上面重点在于步骤t4,当多个线程同时执行到t1,他们看到的R1是一样的,但是当他们执行到t4的时候,数据库会对update的这行记录加锁,确保并发情况下排队执行,所以只有第一个的update会返回1,其他的update结果会返回0,然后后面会判断count是否为1,进而对事务进行提交或者回滚。可以通过count的值知道修改数据是否成功了。

上面这种方式就乐观锁。我们可以通过乐观锁的方式确保数据并发修改过程中的正确性。

使用mysql实现分布式锁

我们创建一个分布式锁表,如下

DROP TABLE IF EXISTS t_lock;
create table t_lock(lock_key varchar(32) PRIMARY KEY NOT NULL COMMENT '锁唯一标志',request_id varchar(64) NOT NULL DEFAULT '' COMMENT '用来标识请求对象的',lock_count INT NOT NULL DEFAULT 0 COMMENT '当前上锁次数',timeout BIGINT NOT NULL DEFAULT 0 COMMENT '锁超时时间',version INT NOT NULL DEFAULT 0 COMMENT '版本号,每次更新+1'
)COMMENT '锁信息表';

java代码如下
mapper接口

package com.shiguiwu.springmybatis.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.shiguiwu.springmybatis.lock.model.LockModel;
import org.springframework.stereotype.Repository;/*** @description: 锁mapper* @author: stone* @date: Created by 2021/5/30 11:12* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.mapper*/
@Repository
public interface LockMapper extends BaseMapper<LockModel> {}

锁对象model

package com.shiguiwu.springmybatis.lock.model;import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;/*** @description: 锁模型* @author: stone* @date: Created by 2021/9/10 11:13* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.lock.model*/
@Data
@TableName("t_lock")
public class LockModel {/*** 锁的唯一值*/@TableIdprivate String lockKey;/*** 请求id,同一个线程里请求id一样*/private String requestId;//锁次数private Integer lockCount;//锁超时private Long timeout;//乐观锁版本@Versionprivate Integer version;
}

锁接口

package com.shiguiwu.springmybatis.lock;/*** @description: 锁接口* @author: stone* @date: Created by 2021/9/10 11:40* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.lock*/
public interface ILock<T> {/*** 获取分布式锁,支持重入* @param lockKey 锁可以* @param lockTimeout  持有锁的有效时间,防止死锁* @param getTimeout 获取锁超时时间,* @return 是否锁成功*/public boolean lock(String lockKey, long lockTimeout, int getTimeout) throws Exception;/*** 解锁* @param lockKey 锁key**/public void unlock(String lockKey);/*** 重置锁对象* @param t 锁对象* @return 返回锁记录*/public int restLock(T t);}

锁的实现代码如下

package com.shiguiwu.springmybatis.lock;import cn.hutool.core.util.StrUtil;
import com.shiguiwu.springmybatis.lock.model.LockModel;
import com.shiguiwu.springmybatis.mapper.LockMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;/*** @description: mysql实现分布式锁* @author: stone* @date: Created by 2021/9/10 11:09* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis.lock*/
@Component
@Slf4j
public class MysqlLock implements ILock<LockModel>{static ThreadLocal<String> requestIds = new ThreadLocal<>();@Autowiredprivate LockMapper lockMapper;public String getRequestId() {String requestId = requestIds.get();if (StrUtil.isBlank(requestId)) {requestId = UUID.randomUUID().toString();requestIds.set(requestId);}log.info("获取到的requestId===> {}", requestId);return requestId;}/***  获取锁* @param lockKey 锁可以* @param lockTimeout  持有锁的有效时间,防止死锁* @param getTimeout 获取锁超时时间,* @return*/@Overridepublic boolean lock(String lockKey, long lockTimeout, int getTimeout) throws Exception {log.info(" lock start =======================> {}",lockKey);//从local中获取 请求idString requestId = this.getRequestId();//获取锁的结果boolean lockResult = false;//开始时间long startTime = System.currentTimeMillis();while (true) {LockModel lockModel = lockMapper.selectById(lockKey);if (Objects.nonNull(lockModel)) {//获取锁对象的请求idString reqId = lockModel.getRequestId();//如果是空,表示改锁未被占有if (StrUtil.isBlank(reqId)) {//马上占有它//设置请求idlockModel.setRequestId(requestId);//设置锁次数lockModel.setLockCount(1);//设置超时时间,防止死锁lockModel.setTimeout(System.currentTimeMillis() + lockTimeout);if (lockMapper.updateById(lockModel) == 1) {lockResult = true;break;}}//如果request_id和表中request_id一样表示锁被当前线程持有者,此时需要加重入锁else if (requestId.equals(reqId)) {//可重入锁lockModel.setTimeout(System.currentTimeMillis() + lockTimeout);//设置获取初次lockModel.setLockCount(lockModel.getLockCount() + 1);if (lockMapper.updateById(lockModel) == 1) {lockResult = true;break;}}//不为空,也不相等,说明是其他线程占有else {//锁不是自己的,并且已经超时了,则重置锁,继续重试if (lockModel.getTimeout() < System.currentTimeMillis()) {//未超时,继续重试this.restLock(lockModel);}//如果未超时,休眠100毫秒,继续重试else {if (startTime + getTimeout > System.currentTimeMillis()) {TimeUnit.MILLISECONDS.sleep(100);}else {//防止长时间阻塞break;}}}}//如果是空,就插入一个锁,重新尝试获取锁else {lockModel = new LockModel();//设置锁keylockModel.setLockKey(lockKey);lockMapper.insert(lockModel);}}log.info(" lock end =======================> {}",lockKey);return lockResult;}/*** 释放锁* @param lockKey 锁key*/@Overridepublic void unlock(String lockKey) {LockModel lockModel = lockMapper.selectById(lockKey);//获取当前线程的请求idString reqId = this.getRequestId();//获取锁次数int count = 0;//当前线程requestId和库中request_id一致 && lock_count>0,表示可以释放锁if (Objects.nonNull(lockModel)&& reqId.equals(lockModel.getRequestId())&& (count = lockModel.getLockCount()) > 0) {if (count == 1) {//重置锁this.restLock(lockModel);}//重入锁的问题,锁的次数减一else {lockModel.setLockCount(lockModel.getLockCount() - 1);//更新次数lockMapper.updateById(lockModel);}}}/*** 重置锁* @param lockModel 锁对象* @return 更新条数*/@Overridepublic int restLock(LockModel lockModel) {lockModel.setLockCount(0);lockModel.setRequestId("");lockModel.setTimeout(0L);return lockMapper.updateById(lockModel);}}

上面代码中实现了文章开头列的分布式锁的所有功能,大家可以认真研究下获取锁的方法:lock,释放锁的方法:unlock。

测试用例

package com.shiguiwu.springmybatis;import com.shiguiwu.springmybatis.lock.ILock;
import com.shiguiwu.springmybatis.lock.model.LockModel;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;/*** @description: 锁测试* @author: stone* @date: Created by 2021/9/10 15:32* @version: 1.0.0* @pakeage: com.shiguiwu.springmybatis*/
@SpringBootTest
@Slf4j
public class LockApplicationTests {@Autowiredprivate ILock<LockModel> mysqlLock;测试重复获取和重复释放@Testpublic void testRepeat() throws Exception {for (int i = 0; i < 10; i++) {mysqlLock.lock("key1", 10000L, 1000);}for (int i = 0; i < 10; i++) {mysqlLock.unlock("key1");}}//    //获取之后不释放,超时之后被thread1获取@Testpublic void testTimeout() throws Exception {String lockKey = "key2";mysqlLock.lock(lockKey, 5000L, 1000);Thread thread1 = new Thread(() -> {try {mysqlLock.lock(lockKey, 5000L, 7000);} catch (Exception e) {e.printStackTrace();} finally {mysqlLock.unlock(lockKey);}}, "thread1");thread1.start();thread1.join();}}

test1方法测试了重入锁的效果。
test2测试了主线程获取锁之后一直未释放,持有锁超时之后被thread1获取到了

留给大家一个问题

上面分布式锁还需要考虑一个问题:比如A机会获取了key1的锁,并设置持有锁的超时时间为10秒,但是获取锁之后,执行了一段业务操作,业务操作耗时超过10秒了,此时机器B去获取锁时可以获取成功的,此时会导致A、B两个机器都获取锁成功了,都在执行业务操作,这种情况应该怎么处理?大家可以思考一下然后留言,我们一起讨论一下。

相关文章:

MySql如何实现分布式锁

本篇我们使用mysql实现一个分布式锁。 环境&#xff1a;mysql8,navicat,maven,springboot2.3.11,mybatis-plus 分布式锁的功能 1,分布式锁使用者位于不同的机器中&#xff0c;锁获取成功之后&#xff0c;才可以对共享资源进行操作 2,锁具有重入的功能&#xff1a;即一个使用…...

「行内揭秘」 SQLynx数据库界的“小众宝藏”?

数据库界的“小众宝藏”&#xff1f;Navicat老大哥地位稳如泰山&#xff0c;但这位“SQLynx”小弟也不容小觑&#xff01;&#x1f440; 别看它小众&#xff0c;SQLynx在处理数据库事务上那可是丝毫不含糊&#xff0c;无论你是Windows Linux和Mac&#xff0c;甚至银河麒麟统信都…...

【已解决】【MySQL】IDEA配置数据库 报错 未配置SQL方言 无法使用SQL提示

IDEA配置数据库的步骤 下载插件 添加数据源 新建--->选择数据源MySQL 页面展示&#xff1a; 主机名&#xff1a;一般都是localhost不用改端口&#xff1a;填写自己的端口号用户&#xff1a;填写自己的用户名密码&#xff1a;填写自己设置的密码数据库&#xff1a;填写需要…...

js 通过filter 实现扁平化数据tree

...

Android 开发 调节声音 SeekBar自定义样式

效果图 xml布局 mipmap/seekbar图片随意一张图都可以&#xff0c;这里我的图就不贴出来了 <SeekBarandroid:id"id/seekBar"android:layout_marginLeft"8dp"android:layout_width"377dp"android:layout_height"8dp"android:layou…...

UART-通用异步收发器

1. UART的基本工作原理 UART通信主要有两个部分构成&#xff1a;发送器和接收器&#xff0c;也就是我们常见的&#xff08;RX接收&#xff0c;TX发送&#xff09;两个独立的线路来实现数据的双向传输&#xff0c;由于是异步的&#xff0c;UART并不需要时钟信号&#xff0c;而是…...

Linux——— 信号

文章目录 前言&#xff1a;引入信号生活中的例子信号概念见一见Linux中的信号 浅度理解信号信号处理&#xff08;浅谈&#xff09;:如何自定义捕捉 信号保存&#xff08;浅谈&#xff09; 信号产生系统调用产生异常产生&#xff1a;浅谈除0异常浅谈解引用野指针异常Core &&…...

安全见闻-web安全

web安全 一、web程序简介 1. Web程序的基本构成 2. 工作流程 3. 安全性 二、JavaScript代码库 1. 代码库的概念和用途 2. 常见的代码库 三、框架 1. 常见的前端框架 2. 常见的后端框架 四、数据库 1. 数据库的分类 2. 数据库的潜在漏洞 3. 学习数据库的重要性 五、…...

华为手机卸载系统应用的方法

摘要&#xff1a; 1.手机环境&#xff1a;手机需要开启开发者模式并使用usb连接电脑&#xff0c;并选择文件传输模式 2.电脑环境&#xff1a;使用鸿蒙工具箱进行傻瓜操作或安装adb工具进行命令卸载 3.鸿蒙工具箱和adb工具本质都是使用adb shell pm uninstall -k --user 0 xx…...

力扣算法笔记——生成随机数组

题目信息: 给两个随机数和N&#xff0c;生成M到N的随机不重复数组&#xff0c;且M<N。 示例&#xff1a;输入M 2, N5, 输出 [4,3,2,5]. 思路&#xff1a;洗牌算法&#xff0c;先遍历M到N之间所有的数字&#xff0c;将得到的结果存入一个集合中&#xff0c;将集合从后往前遍历…...

Anaconda和Pycharm超详细安装教程(2024版本+Win11)

详细安装&#xff1a;https://download.csdn.net/download/qq_40379132/89924782 一、安装Anaconda 1.1 下载Anaconda 在官方网站&#xff08;Free Download | Anaconda&#xff09;上下载适用于你的操作系统的 Anaconda 安装包。&#xff08;这里以windows为例&#xff09;…...

代码随想录:从中后/中前遍历序列构造二叉树

106. 从中序与后序遍历序列构造二叉树 用分治思想&#xff0c;后序遍历是左右中&#xff0c;中序遍历是左中右&#xff0c;后序遍历的最后一个元素就是根节点&#xff0c; 在中序遍历中找到它的位置&#xff0c;它前面的为左子树&#xff0c;后面的为右子树&#xff0c;并能计…...

2-134 基于matlab的图像边缘检测

基于matlab的图像边缘检测&#xff0c;采用六种算子(分别是gabor、拉普拉斯、priwitt、robert、sobel、wallis微分算子&#xff09;&#xff0c;对图象进行边缘检测比较&#xff0c;输出边缘检测结果。可对比效果优劣。程序已调通&#xff0c;可直接运行。 下载源程序请点链接…...

【Java并发编程】线程池详解

一、简介 随着计算机行业的飞速发展&#xff0c;摩尔定律逐渐失效&#xff0c;多核CPU成为主流。使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器。J.U.C提供的线程池&#xff1a;ThreadPoolExecutor 类&#xff0c;帮助开发人员管理线程并方便地执行并行任务。了…...

ThingsBoard规则链节点:GPS Geofencing Events节点详解

引言 1. GPS Geofencing Events 节点简介 2. 节点配置 3. 使用场景 3.1 物流跟踪 3.2 资产管理 3.3 安全监控 3.4 农业监测 4. 实际项目中的应用 4.1 项目背景 4.2 项目需求 4.3 实现步骤 5. 总结 引言 GPS Geofencing Events 是 ThingsBoard 规则链中的一个重要节…...

Jmeter基础篇(19)JSR223预处理器

前言 JSR223预处理器是Apache JMeter中的一个组件&#xff0c;它允许用户使用任何支持Java Scripting API (JSR 223) 的脚本语言来执行预处理任务。这个功能非常强大&#xff0c;因为它让测试人员能够利用如Groovy、JavaScript&#xff08;Nashorn引擎&#xff09;、BeanShell…...

通过js控制css变量

在JavaScript中&#xff0c;你可以通过操作CSS变量&#xff08;也称为自定义属性&#xff09;来动态改变样式。CSS变量在CSS中使用 – 前缀定义&#xff0c;例如 --main-color: red;。在JavaScript中&#xff0c;你可以使用 document.documentElement.style.setProperty 方法来…...

Docker:容器化和虚拟化

虚拟化 虚拟化是一种资源管理技术&#xff0c;它将计算机的各种实体资源&#xff08;如CPU、内存、磁盘空间、网络适配器等&#xff09;予以抽象、转换后呈现出来&#xff0c;并可供分割、组合为一个或多个电脑配置环境。这些资源的新虚拟部分是不受现有资源的架设方式、地域或…...

OpenSSL

OpenSSL 概述 OpenSSL 是一个开源的、安全传输协议实现工具&#xff0c;广泛应用于数据加密与解密、证书生成与管理以及其他安全性相关的任务。在现代网络安全中&#xff0c;OpenSSL 被用于构建和维护 SSL/TLS 通信&#xff0c;确保数据在传输过程中的机密性和完整性。 简单来…...

CSS 常见选择器

1. 基础选择器 元素选择器 选择所有指定类型的 HTML 元素。 p {color: blue; }选择所有 p 标签&#xff0c;并将文字颜色设为蓝色。 类选择器 选择带有特定类名的元素&#xff0c;类名前加 .。 .container {margin: 20px; }选择类名为 container 的所有元素。 ID 选择器 选…...

从“工具辅助”到“智慧赋能”:青软青之深度集成LIMS、ELN、AUTO等核心系统,打造全场景智慧实验室新范式

在科研创新迭代加速、检验检测产业升级纵深推进的今天&#xff0c;实验室作为创新源头&#xff0c;其运行效率与管理水平直接决定研发效能与质量。传统依赖人工记录、纸质流转和信息孤岛的模式&#xff0c;已难以适应复杂实验需求与严苛合规监管。智慧实验室&#xff0c;正成为…...

OpenClaw多模态聊天机器人:Qwen2.5-VL-7B实现图片问答与表情包生成

OpenClaw多模态聊天机器人&#xff1a;Qwen2.5-VL-7B实现图片问答与表情包生成 1. 为什么选择OpenClaw构建多模态聊天机器人 去年我在运营一个技术社群时&#xff0c;经常遇到群成员发截图提问的场景。传统聊天机器人要么只能处理文字&#xff0c;要么需要将图片上传到第三方…...

Android安全漏洞案例分析:血淋淋的教训

Android安全漏洞案例分析&#xff1a;血淋淋的教训 Android安全漏洞案例分析&#xff1a;血淋淋的教训 案例一&#xff1a;Secret Token泄露导致账户劫持 漏洞危害&#xff1a;攻击者获取用户全部权限 某社交App在客户端硬编码了API密钥&#xff0c;攻击者通过反编译获取密钥…...

Polars 2.0清洗性能天花板在哪?实测对比Dask/Modin/Vaex:单机1TB数据清洗仅需11.3秒(附完整安装脚本)

第一章&#xff1a;Polars 2.0 大规模数据清洗技巧Polars 2.0 引入了更严格的惰性执行模型、增强的字符串与时间处理能力&#xff0c;以及原生支持多线程 I/O 的 LazyFrame API&#xff0c;显著提升了 TB 级数据清洗的吞吐与可控性。相比 Pandas&#xff0c;其列式内存布局与零…...

别再手动标图了!用CVAT和YOLOv5搭建半自动标注流水线(保姆级避坑指南)

从零构建CVATYOLOv5半自动标注系统&#xff1a;工程化实践与效率革命 标注数据是AI开发中最耗时却无法绕过的环节。我曾为一个客户项目标注3万张工业零件图像&#xff0c;团队3人整整耗费两周——直到发现CVAT与训练好的YOLOv5模型结合&#xff0c;能将效率提升400%。本文将分…...

Real-ESRGAN-GUI:如何用AI双引擎将模糊图片一键变高清

Real-ESRGAN-GUI&#xff1a;如何用AI双引擎将模糊图片一键变高清 【免费下载链接】Real-ESRGAN-GUI Lovely Real-ESRGAN / Real-CUGAN GUI Wrapper 项目地址: https://gitcode.com/gh_mirrors/re/Real-ESRGAN-GUI 还在为模糊的老照片、低分辨率的动漫图片而烦恼吗&…...

从Simulink到实物:单闭环直流调速仿真如何指导真实的Arduino/STM32控制?

从Simulink到Arduino&#xff1a;如何将直流电机控制算法从仿真落地到真实硬件 当你第一次在Simulink中看到那个完美的电机转速响应曲线时&#xff0c;那种成就感是无可替代的。但很快&#xff0c;一个更迫切的问题出现了&#xff1a;这些漂亮的仿真结果&#xff0c;如何变成手…...

ModTheSpire模组加载器全攻略:解锁杀戮尖塔无限可能

ModTheSpire模组加载器全攻略&#xff1a;解锁杀戮尖塔无限可能 【免费下载链接】ModTheSpire External mod loader for Slay The Spire 项目地址: https://gitcode.com/gh_mirrors/mo/ModTheSpire 副标题&#xff1a;从零开始的模组探索之旅——让你的游戏体验突破边界…...

AI报告文档审核助力生态数据可信化:IACheck提升生物多样性调查报告物种识别准确性

在生态环境保护逐渐走向精细化管理的背景下&#xff0c;生物多样性调查数据的重要性不断提升。从自然保护区评估到生态修复项目&#xff0c;从环境影响评价到长期生态监测&#xff0c;物种数据已成为支撑决策的重要基础。而在这些数据中&#xff0c;“物种识别的准确性”&#…...

终极指南:3步打造你的闲鱼AI客服机器人,实现24小时自动化值守

终极指南&#xff1a;3步打造你的闲鱼AI客服机器人&#xff0c;实现24小时自动化值守 【免费下载链接】XianyuAutoAgent 智能闲鱼客服机器人系统&#xff1a;专为闲鱼平台打造的AI值守解决方案&#xff0c;实现闲鱼平台724小时自动化值守&#xff0c;支持多专家协同决策、智能议…...