【SpringBoot篇】基于Redis分布式锁的 误删问题 和 原子性问题
文章目录
- ??Redis的分布式锁
- ??误删问题
-
- ??解决方法
- ??代码实现
- ??原子性问题
-
-
- ??Lua脚本
- ?利用Java代码调用Lua脚本改造分布式锁
- ??代码实现
-

??Redis的分布式锁
Redis的分布式锁是通过利用Redis的原子操作和特性来实现的。在分布式环境中,多个应用程序或服务可能同时访问共享资源,为了保证数据的一致性和避免冲突,可以使用分布式锁来进行同步控制。
以下是一种常见的使用Redis实现分布式锁的方式:
-
获取锁:当一个应用程序需要获取锁时,它可以通过执行以下操作在Redis中设置一个特定的键值对:
SET lock_key unique_value NX PX lock_timeout
这里的lock_key是锁的唯一标识,unique_value是唯一的值,可以是随机生成的UUID,NX表示只有当键不存在时才会设置成功,PX表示设置键的过期时间。通过设置过期时间,即使获取锁的应用程序崩溃或异常退出,锁也会在一段时间后自动释放,避免出现死锁。
-
释放锁:当应用程序完成对共享资源的操作后,它可以通过执行以下操作释放锁:
if GET lock_key == unique_value then
DELETE lock_key
end
应用程序首先获取锁的当前值,然后比较是否与自己持有的唯一值相等,如果相等则删除该键,表示释放锁。这样可以确保只有持有锁的应用程序才能释放锁,避免误释放其他应用程序的锁。
需要注意的是,分布式锁并不是绝对安全和可靠的。在高并发的环境中,可能存在竞争条件和死锁等问题。因此,在实际使用中,需要考虑更复杂的场景和解决方案。
??误删问题
遇到下面的情况的话,会出现Redis分布式锁的误删问题

这种情况下。线程1首先获取锁,但是发生了阻塞,于是线程2拿到了执行权,在线程2执行的过程中,线程1苏醒了,继续执行,到后面,线程1执行到了删除锁的操作,此时就会把本应该属于线程2的锁删除,这样子就造成了误删问题
??解决方法
就是在每个线程释放锁的时候,去判断一下当前这把锁是否属于自己,如果属于自己,则不进行锁的删除,假设还是上边的情况,线程1卡顿,锁自动释放,线程2进入到锁的内部执行逻辑,此时线程1反应过来,然后删除锁,但是线程1,一看当前这把锁不是属于自己,于是不进行删除锁逻辑,当线程2走到删除锁逻辑时,如果没有卡过自动释放锁的时间点,则判断当前这把锁是属于自己的,于是删除这把锁。

??代码实现
public class SimpleRedisLock implements ILock {private String name;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}private static final String KEY_PREFIX = "lock:";//使用uuid,在获取锁的时候存入线程标识private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";@Overridepublic boolean tryLock(long timeoutSec) {// 获取线程标示String threadId = ID_PREFIX + Thread.currentThread().getId();// 获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);//这里不能是return success;否则 因为public后面的boolean是基本类型,而Boolean是引用类型,如果直接返回success,是一个自动拆箱的过程,可能回发生空指针异常}@Overridepublic void unlock() {// 获取线程标示String threadId = ID_PREFIX + Thread.currentThread().getId();// 获取锁中的标示String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);// 判断标示是否一致if(threadId.equals(id)) {// 释放锁stringRedisTemplate.delete(KEY_PREFIX + name);}}
}
??原子性问题
上面我们解决了误删问题
在误删问题的情况下,遇到下面的情况的话,会出现Redis分布式锁的原子性问题

这种情况下,线程1先执行一段,线程1先判断锁标识,判断成功,标识是属于线程1的,后面就在线程1正准备删除锁释放的过程中,突然线程1的锁过期了,线程1发生阻塞
这个时候线程2开始执行,在线程2执行过程中,线程1阻塞结束了,会执行删除锁的操作,相当于判断锁标识并没有起到作用(因为之前一句判断过了),于是就把线程2的锁给删除掉了,又一次发生了误删操作
这个时候线程3趁虚而入,执行业务
这就是删锁时的原子性问题,之所以有这个问题,是因为判断锁标识和删除锁是2个动作,这2个动作中间产生了阻塞
那么我们就要让这2个操作一起执行,中间不能出现间隔
??Lua脚本
Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。Lua是一种编程语言,它的基本语法大家可以参考网站:https://www.runoob.com/lua/lua-tutorial.html,这里重点介绍Redis提供的调用函数,我们可以使用lua去操作redis,又能保证他的原子性,这样就可以实现拿锁比锁删锁是一个原子性动作了,作为Java程序员这一块并不作一个简单要求,并不需要大家过于精通,只需要知道他有什么作用即可。
这里重点介绍Redis提供的调用函数,语法如下:
redis.call('命令名称', 'key', '其它参数', ...)
例如,我们要执行set name jack,则脚本是这样:
# 执行 set name jack
redis.call('set', 'name', 'jack')
例如,我们要先执行set name Rose,再执行get name,则脚本如下:
# 先执行 set name jack
redis.call('set', 'name', 'Rose')
# 再执行 get name
local name = redis.call('get', 'name')
# 返回
return name
写好脚本以后,需要用Redis命令来调用脚本,调用脚本的常见命令如下:

例如,我们要执行 redis.call(‘set’, ‘name’, ‘jack’) 这个脚本,语法如下:

如果脚本中的key、value不想写死,可以作为参数传递。key类型参数会放入KEYS数组,其它参数会放入ARGV数组,在脚本中可以从KEYS和ARGV数组获取这些参数:

利用Java代码调用Lua脚本改造分布式锁
接下来我们来回一下我们释放锁的逻辑:
释放锁的业务流程是这样的
1、获取锁中的线程标示
2、判断是否与指定的标示(当前线程标示)一致
3、如果一致则释放锁(删除)
4、如果不一致则什么都不做
如果用Lua脚本来表示则是这样的:
最终我们操作redis的拿锁比锁删锁的lua脚本就会变成这样
-- 这里的 KEYS[1] 就是锁的key,这里的ARGV[1] 就是当前线程标示
-- 获取锁中的标示,判断是否与当前线程标示一致
if (redis.call('GET', KEYS[1]) == ARGV[1]) then-- 一致,则删除锁return redis.call('DEL', KEYS[1])
end
-- 不一致,则直接返回
return 0
lua脚本本身并不需要大家花费太多时间去研究,只需要知道如何调用,大致是什么意思即可,所以在笔记中并不会详细的去解释这些lua表达式的含义。
我们的RedisTemplate中,可以利用execute方法去执行lua脚本,参数对应关系就如下图


??代码实现
我们先写入lua这个脚本

-- 比较线程标示与锁中的标示是否一致
if(redis.call('get', KEYS[1]) == ARGV[1]) then-- 释放锁 del keyreturn redis.call('del', KEYS[1])
end
return 0
然后我们来调用这个脚本

下面是完整代码
public class SimpleRedisLock implements ILock {private String name;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}private static final String KEY_PREFIX = "lock:";private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;static {UNLOCK_SCRIPT = new DefaultRedisScript<>();UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));UNLOCK_SCRIPT.setResultType(Long.class);}@Overridepublic boolean tryLock(long timeoutSec) {// 获取线程标示String threadId = ID_PREFIX + Thread.currentThread().getId();// 获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);//这里不能是return success;否则 因为public后面的boolean是基本类型,而Boolean是引用类型,如果直接返回success,是一个自动拆箱的过程,可能回发生空指针异常}@Overridepublic void unlock() {// 调用lua脚本stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX + name),ID_PREFIX + Thread.currentThread().getId());}
}
在技术的道路上,我们不断探索、不断前行,不断面对挑战、不断突破自我。科技的发展改变着世界,而我们作为技术人员,也在这个过程中书写着自己的篇章。让我们携手并进,共同努力,开创美好的未来!愿我们在科技的征途上不断奋进,创造出更加美好、更加智能的明天!

相关文章:
【SpringBoot篇】基于Redis分布式锁的 误删问题 和 原子性问题
文章目录 ??Redis的分布式锁??误删问题 ??解决方法??代码实现 ??原子性问题 ??Lua脚本 ?利用Java代码调用Lua脚本改造分布式锁??代码实现 ??Redis的分布式锁 Redis的分布式锁是通过利用Redis的原子操作和特性来实现的。在分布式环境中,多个应用…...
【JVM详解三】垃圾回收机制
一、对象是否存活 强引用:Object obj new Object(); 只要强引用还在,垃圾收集器永远不会回收掉被引用的对象。在不用对象的时将引用赋值为 null,能够帮助垃圾回收器回收对象。比如 ArrayList 的 clear() 方法实现。软引用(SoftRe…...
MySQL的字符集(Character Set)和排序规则(Collation)
MySQL的字符集(Character Set)和排序规则(Collation) 字符集(Character Set)和排序规则(Collation)是数据库中处理文本数据的两个核心概念,二者紧密相关但作用不同。 1…...
2025影视泛目录站群程序设计_源码二次开发新版本无缓存刷新不变实现原理
1. 引言 本设站群程序计书旨在详细阐述苹果CMS泛目录的创新设计与实现,介绍无缓存刷新技术、数据统一化、局部URL控制及性能优化等核心功能,以提升网站访问速度和用户体验。 2. 技术概述 2.1 无缓存刷新技术 功能特点: 内容不变性&#x…...
常用的python库-安装与使用
常用的python库函数 yield关键字openslide库openslide库的安装-linuxopenslide的使用openslide对象的常用属性 cv2库numpy库ASAP库-multiresolutionimageinterface库ASAP库的安装ASAP库的使用 concurrent.futures.ThreadPoolExecutorxml.etree.ElementTree库skimage库PIL.Image…...
array_walk. array_map. array_filter
1. array_walk 函数 array_walk 用于遍历数组并对每个元素执行回调函数。它不会受到数组内部指针位置的影响,会遍历整个数组。回调函数接收的前两个参数分别是元素的值和键名,如果有第三个参数,则数组所有的值都共用这个参数。 示例代码&am…...
数据仓库和商务智能:洞察数据,驱动决策
在数据管理的众多领域中,数据仓库和商务智能(BI)是将数据转化为洞察力、支持决策制定的关键环节。它们通过整合、存储和分析数据,帮助组织更好地理解业务运营,预测市场趋势,从而制定出更明智的战略。今天&a…...
Vue设计模式到底多少种?
Vue设计模式到底多少种? 很多同学问,Vue到底有多少种设计模式??各个模式到底是什么意思??又各自适合什么场景?? 这里我给大家直接说下,Vue的设计模式没有一个固定的数值…...
HTML 属性
HTML 属性 HTML(超文本标记语言)是构建网页的基础,而HTML属性则是赋予HTML元素额外功能和样式的关键。本文将详细介绍HTML属性的概念、常用属性及其应用,帮助您更好地理解和使用HTML。 一、HTML属性概述 HTML属性是HTML元素的组成部分,用于描述元素的状态或行为。属性总…...
oracle如何查询历史最大进程数?
oracle如何查询历史最大进程数? SQL> desc dba_hist_resource_limitName Null? Type---------------------------------------------------- -------- ------------------------------------SNAP_ID …...
SpringBoot单机模式,能否支持一万用户请求并发?
Spring Boot 单机模式能否支持一万用户请求并发,取决于多个因素: 硬件配置:CPU、内存、磁盘I/O和网络带宽是关键。高性能硬件能显著提升并发处理能力。 应用复杂度:业务逻辑复杂度和数据库操作频率会影响性能。复杂的业务逻辑和高…...
[前端]CRX持久化
在 Chrome 扩展开发中,持久化保存数据通常使用 Chrome 的 storage API。storage API 提供了两种存储选项:local 和 sync。使用 local 存储的数据保存在本地浏览器中,只能在同一设备上访问。使用 sync 存储的数据可以在用户登录其 Google 帐户…...
模型 替身决策
系列文章分享模型,了解更多👉 模型_思维模型目录。替身决策,换位思考,多角度决策。 1 替身决策模型的应用 1.1 替身决策模型在面试中的应用-小李的求职面试 小李是一名应届毕业生,正在积极寻找工作机会。在面试过程中…...
【系统架构设计师】体系结构文档化
目录 1. 说明2. 重要性3. 主要内容4. 编写原则5. 实践建议6. 例题6.1 例题1 1. 说明 1.绝大多数的体系结构都是抽象的,由一些概念上的构建组成。2.层的概念在任何程序设计语言中都不存在。3.要让系统分析员和程序员去实现体系结构,还必须将体系结构进行…...
Python Pandas(5):Pandas Excel 文件操作
Pandas 提供了丰富的 Excel 文件操作功能,帮助我们方便地读取和写入 .xls 和 .xlsx 文件,支持多表单、索引、列选择等复杂操作,是数据分析中必备的工具。 操作方法说明读取 Excel 文件pd.read_excel()读取 Excel 文件,返回 DataF…...
区块链技术:Facebook 重塑社交媒体信任的新篇章
在这个信息爆炸的时代,社交媒体已经成为我们生活中不可或缺的一部分。然而,随着社交平台的快速发展,隐私泄露、数据滥用和虚假信息等问题也日益凸显。这些问题的核心在于传统社交媒体依赖于中心化服务器存储和管理用户数据,这种模…...
跨平台App开发,有哪些编程语言和工具,比较一下优劣势?
1. React Native 语言:JavaScript 工具:React Native框架 优势: 跨平台支持:一套代码可同时运行在iOS和Android上。 社区支持:拥有庞大的社区和丰富的第三方库。 热更新:支持热更新,无需重新…...
Windows逆向工程入门之汇编环境搭建
公开视频 -> 链接点击跳转公开课程博客首页 -> 链接点击跳转博客主页 Visual Studio逆向工程配置 基础环境搭建 Visual Studio 官方下载地址安装配置选项(后期可随时通过VS调整) 使用C的桌面开发 拓展可选选项 MASM汇编框架 配置MASM汇编项目 创建新项目 选择空…...
网络安全溯源 思路 网络安全原理
网络安全背景 网络就是实现不同主机之间的通讯。网络出现之初利用TCP/IP协议簇的相关协议概念,已经满足了互连两台主机之间可以进行通讯的目的,虽然看似简简单单几句话,就描述了网络概念与网络出现的目的,但是为了真正实现两台主机…...
《Peephole LSTM:窥视孔连接如何开启性能提升之门》
在深度学习的领域中,长短期记忆网络(LSTM)以其出色的序列数据处理能力而备受瞩目。而Peephole LSTM作为LSTM的一种重要变体,通过引入窥视孔连接,进一步提升了模型的性能。那么,窥视孔连接究竟是如何发挥作用…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)
目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 (1)输入单引号 (2)万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...
LangChain【6】之输出解析器:结构化LLM响应的关键工具
文章目录 一 LangChain输出解析器概述1.1 什么是输出解析器?1.2 主要功能与工作原理1.3 常用解析器类型 二 主要输出解析器类型2.1 Pydantic/Json输出解析器2.2 结构化输出解析器2.3 列表解析器2.4 日期解析器2.5 Json输出解析器2.6 xml输出解析器 三 高级使用技巧3…...
解决MybatisPlus使用Druid1.2.11连接池查询PG数据库报Merge sql error的一种办法
目录 前言 一、问题重现 1、环境说明 2、重现步骤 3、错误信息 二、关于LATERAL 1、Lateral作用场景 2、在四至场景中使用 三、问题解决之道 1、源码追踪 2、关闭sql合并 3、改写处理SQL 四、总结 前言 在博客:【写在创作纪念日】基于SpringBoot和PostG…...
Neo4j 完全指南:从入门到精通
第1章:Neo4j简介与图数据库基础 1.1 图数据库概述 传统关系型数据库与图数据库的对比图数据库的核心优势图数据库的应用场景 1.2 Neo4j的发展历史 Neo4j的起源与演进Neo4j的版本迭代Neo4j在图数据库领域的地位 1.3 图数据库的基本概念 节点(Node)与关系(Relat…...
