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实现一个分布式锁。 环境:mysql8,navicat,maven,springboot2.3.11,mybatis-plus 分布式锁的功能 1,分布式锁使用者位于不同的机器中,锁获取成功之后,才可以对共享资源进行操作 2,锁具有重入的功能:即一个使用…...
「行内揭秘」 SQLynx数据库界的“小众宝藏”?
数据库界的“小众宝藏”?Navicat老大哥地位稳如泰山,但这位“SQLynx”小弟也不容小觑!👀 别看它小众,SQLynx在处理数据库事务上那可是丝毫不含糊,无论你是Windows Linux和Mac,甚至银河麒麟统信都…...
【已解决】【MySQL】IDEA配置数据库 报错 未配置SQL方言 无法使用SQL提示
IDEA配置数据库的步骤 下载插件 添加数据源 新建--->选择数据源MySQL 页面展示: 主机名:一般都是localhost不用改端口:填写自己的端口号用户:填写自己的用户名密码:填写自己设置的密码数据库:填写需要…...
Android 开发 调节声音 SeekBar自定义样式
效果图 xml布局 mipmap/seekbar图片随意一张图都可以,这里我的图就不贴出来了 <SeekBarandroid:id"id/seekBar"android:layout_marginLeft"8dp"android:layout_width"377dp"android:layout_height"8dp"android:layou…...
UART-通用异步收发器
1. UART的基本工作原理 UART通信主要有两个部分构成:发送器和接收器,也就是我们常见的(RX接收,TX发送)两个独立的线路来实现数据的双向传输,由于是异步的,UART并不需要时钟信号,而是…...
Linux——— 信号
文章目录 前言:引入信号生活中的例子信号概念见一见Linux中的信号 浅度理解信号信号处理(浅谈):如何自定义捕捉 信号保存(浅谈) 信号产生系统调用产生异常产生:浅谈除0异常浅谈解引用野指针异常Core &&…...
安全见闻-web安全
web安全 一、web程序简介 1. Web程序的基本构成 2. 工作流程 3. 安全性 二、JavaScript代码库 1. 代码库的概念和用途 2. 常见的代码库 三、框架 1. 常见的前端框架 2. 常见的后端框架 四、数据库 1. 数据库的分类 2. 数据库的潜在漏洞 3. 学习数据库的重要性 五、…...
华为手机卸载系统应用的方法
摘要: 1.手机环境:手机需要开启开发者模式并使用usb连接电脑,并选择文件传输模式 2.电脑环境:使用鸿蒙工具箱进行傻瓜操作或安装adb工具进行命令卸载 3.鸿蒙工具箱和adb工具本质都是使用adb shell pm uninstall -k --user 0 xx…...
力扣算法笔记——生成随机数组
题目信息: 给两个随机数和N,生成M到N的随机不重复数组,且M<N。 示例:输入M 2, N5, 输出 [4,3,2,5]. 思路:洗牌算法,先遍历M到N之间所有的数字,将得到的结果存入一个集合中,将集合从后往前遍历…...
Anaconda和Pycharm超详细安装教程(2024版本+Win11)
详细安装:https://download.csdn.net/download/qq_40379132/89924782 一、安装Anaconda 1.1 下载Anaconda 在官方网站(Free Download | Anaconda)上下载适用于你的操作系统的 Anaconda 安装包。(这里以windows为例)…...
代码随想录:从中后/中前遍历序列构造二叉树
106. 从中序与后序遍历序列构造二叉树 用分治思想,后序遍历是左右中,中序遍历是左中右,后序遍历的最后一个元素就是根节点, 在中序遍历中找到它的位置,它前面的为左子树,后面的为右子树,并能计…...
2-134 基于matlab的图像边缘检测
基于matlab的图像边缘检测,采用六种算子(分别是gabor、拉普拉斯、priwitt、robert、sobel、wallis微分算子),对图象进行边缘检测比较,输出边缘检测结果。可对比效果优劣。程序已调通,可直接运行。 下载源程序请点链接…...
【Java并发编程】线程池详解
一、简介 随着计算机行业的飞速发展,摩尔定律逐渐失效,多核CPU成为主流。使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器。J.U.C提供的线程池:ThreadPoolExecutor 类,帮助开发人员管理线程并方便地执行并行任务。了…...
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中的一个组件,它允许用户使用任何支持Java Scripting API (JSR 223) 的脚本语言来执行预处理任务。这个功能非常强大,因为它让测试人员能够利用如Groovy、JavaScript(Nashorn引擎)、BeanShell…...
通过js控制css变量
在JavaScript中,你可以通过操作CSS变量(也称为自定义属性)来动态改变样式。CSS变量在CSS中使用 – 前缀定义,例如 --main-color: red;。在JavaScript中,你可以使用 document.documentElement.style.setProperty 方法来…...
Docker:容器化和虚拟化
虚拟化 虚拟化是一种资源管理技术,它将计算机的各种实体资源(如CPU、内存、磁盘空间、网络适配器等)予以抽象、转换后呈现出来,并可供分割、组合为一个或多个电脑配置环境。这些资源的新虚拟部分是不受现有资源的架设方式、地域或…...
OpenSSL
OpenSSL 概述 OpenSSL 是一个开源的、安全传输协议实现工具,广泛应用于数据加密与解密、证书生成与管理以及其他安全性相关的任务。在现代网络安全中,OpenSSL 被用于构建和维护 SSL/TLS 通信,确保数据在传输过程中的机密性和完整性。 简单来…...
CSS 常见选择器
1. 基础选择器 元素选择器 选择所有指定类型的 HTML 元素。 p {color: blue; }选择所有 p 标签,并将文字颜色设为蓝色。 类选择器 选择带有特定类名的元素,类名前加 .。 .container {margin: 20px; }选择类名为 container 的所有元素。 ID 选择器 选…...
Linux使用Dockerfile部署Tomcat以及jdk
资源准备 首先提供本教程所有资源包。 当然也可以根据自己需求去官网下载。 链接:百度网盘 请输入提取码 提取码:f31y #我们开始吧 首先我们需要一台linux操作系统的机器,当然windows也是可以的,本系列教程是基于Linux的&#…...
LC20. 有效的括号
用来熟悉一下栈的应用之括号匹配 题目链接 下面是大致思路 1.初始化:创建一个空栈,用于存储左括号。(LC这题不用,自己写完整的需要) 2.遍历字符串:从左到右依次扫描字符串中的每个字符。 3.处理左括号:如果是左括号,将其压入栈中。 4.处理右…...
基于springboot企业微信SCRM管理系统源码带本地搭建教程
系统是前后端分离的架构,前端使用Vue2,后端使用SpringBoot2。 技术框架:SpringBoot2.0.0 Mybatis1.3.2 Shiro swagger-ui jpa lombok Vue2 Mysql5.7 运行环境:jdk8 IntelliJ IDEA maven 宝塔面板 系统与功能介绍 基…...
【MTMSA】不确定缺失模态下基于情态翻译的多模态情感分析
MTMSA是基于TATE改进的,大致框架都和他一样,区别在于MTMSA没有提到tag,并且在多头注意力的部分进行了改进,也就是文中模态翻译模块,此外还加了两个损失函数。在TATE中有一章是不同设置的影响,里面有多个证明…...
【php常用公共函数】php获取指定时间段中有那几年并输出年份的起始时间和结束时间
php获取指定时间段中有那几年并输出年份的起始时间和结束时间 实现思路实现代码输出结果 实现思路 解析输入的时间:将输入的时间字符串转换为DateTime对象。计算年份范围:从开始年份到结束年份,生成一个包含所有年份的数组。输出年份的起始和…...
CTF-PWN: 什么是_IO_FILE?
重要概念:fopen()返回的是一个结构体的指针 _IO_FILE 结构体在什么时候被创建? _IO_FILE 结构体的实例是在程序使用标准 I/O 函数(如 fopen、fclose、fread、fwrite 等)时创建和管理的。这个结构体实际上是 GNU C Library (glibc) 用于处理…...
前端八股文第二篇
11.事件循环 事件循环(Event Loop)是 JavaScript 运行时中的一种机制,用于处理异步操作和事件驱动的编程。在浏览器环境中,事件循环是指浏览器通过事件队列(Event Queue)来管理和调度异步任务的执行顺序。…...
springboot汽车保修服务管理系统-计算机毕业设计源码00052
摘 要 随着汽车数量的不断增加和汽车保修服务需求的日益增长,建立一套高效的汽车保修服务管理系统变得至关重要。基于Spring Boot框架的汽车保修服务管理系统旨在整合汽车保修流程,简化管理流程,提高服务质量和用户体验未来,我们将…...
分布式架构搭建博客网站
目录 运行环境基础配置需求准备工作配置静态ip修改主机名及host映射开启防火墙时间同步配置免密ssh登录 环境搭建Server-Web端安装LNMP环境软件Server-NFS-DNS端上传博客软件Server-NFS-DNS端设置NFS共享Server-Web设置挂载远程共享目录nginx设置在数据库中创建数据库和用户重启…...
python-opencv给图片或视频去水印
文章目录 引言inpaint函数的使用方法鼠标事件回调函数cv2.setMouseCallback介绍去水印步骤实现代码 引言 本文主要基于cv2.inpaint函数实现图片的水印去除。 inpaint函数基于图像修复算法,通过对缺陷区域周围像素的分析和插值,生成合适的像素值来填充缺…...
nft制作网站/电商平台排行榜
这是因为中国的企业都没有长远的规划,或者说它们都不知道自己能生存多长时间,如此情况下谁会愿意冒险去尝试新的东西,即使是深圳科技企业其实同样不愿意尝试新的东西,你看它先后进入的手机、PC、服务器、云计算,哪个是它开拓出来的…...
网站开发毕业论文任务书/seo技术培训岳阳
经过前面22小节,我们已经将自己的博客网站搭建完成了,但是只能在本机通过127.0.0.1:8000进行访问,那么如何才能够让别人通过登录自己的网站,在外网也能够访问自己的博客呢?为了完成这个目的,我们需要做如下…...
温州 网站优化/最好用的搜索神器
转载来自:http://blog.csdn.net/bobbat/article/details/51494794../../include/QtCore/../../src/corelib/arch/qatomic_arm.h:131: Error: no such instruction: swpb %cl,%dl,[%edi]可能是arm 交叉环境有问题,如果配置好了一般reconfigure下就ok.如果总是有问题&…...
wordpress链接浏览量/seo常见的优化技术
一、需求: 1、业务需求:根据手机号到数据库中查看用户id,再根据用户id查看该注册用户下关联的健康成员。 2、参数化分析 1)需要根据不同的手机号进行查询,所以手机号需要进行参数化 2)用户id要作为下一个查…...
获取网站访客qq 原理/今日国内新闻大事
可以搜索微信公众号【Jet 与编程】查看更多精彩文章原文发布于自己的博客平台【http://www.jetchen.cn/analysis-hashmap/】HashMap 这个数据结构,不管是日常开发,还是求职面试,它始终都是所有 Java 程序员绕不开的宿命,所以还是决…...
中企动力网站建设精品案例/什么推广方式能快速引流
基于springboot实现协同过滤算法商品推荐系统演示开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea Mav…...