分布式锁优化:使用Lua脚本保证释放锁的原子性问题

article/2025/7/2 21:10:34

分布式锁优化(二):使用Lua脚本保证释放锁的原子性问题

💻黑马视频链接:Lua脚本解决多条命令原子性问题

在上一章节视频实现了一个可用的Redis分布式锁,采用SET NX EX命令实现互斥和过期自动释放机制,并通过给锁绑定线程标识来避免锁误删的问题。

正当我已经感觉非常完美地防止“超卖”问题的时候,虎哥又举了一个更小概率的事件哈哈,判断和删除是两次单独的Redis操作,中间如果线程阻塞或者上下文切换,就可能导致“误删别人的锁”! 于是谈到:释放锁操作的原子性问题。下面将继续优化这把锁,引入Lua脚本,彻底解决这个问题(其实还是不够彻底哈哈~)

一、释放锁不是原子操作

之前我们是这样释放锁的:

String id = redisTemplate.opsForValue().get("lock:order");
if (id.equals(currentThreadId)) {redisTemplate.delete("lock:order");
}

这看起来没问题对吧?我们先判断锁是不是自己的,如果是就删掉。

但问题就在于:两次单独的Redis操作,中间如果阻塞,就可能导致“误删别人的锁”。(如下图)
在这里插入图片描述

举个真实的例子:

  1. 线程1获得锁,执行业务时卡住了(比如GC阻塞或垃圾回收机制)。
  2. 锁超时释放了,线程2趁机获得了锁。
  3. 就在这时线程1恢复了,执行delete()操作,误删了线程2的锁。
  4. 于是线程3也抢到了锁,导致并发执行 → 超卖!

这就像网吧上机,座位是你在用没错,但你出去上厕所的功夫别人坐了你的位置,你回来不管三七二十一直接拔网线……

二、我们需要“原子释放锁”

原子性:一组操作要么全部完成,要么全部不做,中间不允许被打断。

我们需要将“比对线程标识”和“删除锁”这两个操作合并成一个原子操作,要么同时完成,要么都不执行。这样才能避免误删问题。

Redis 本身虽然没有支持“if equals then delete”这种原子命令,但它提供了一种机制——Lua脚本

三、Lua脚本简介:Redis的原子武器

Lua是一门轻量级脚本语言,Redis支持在服务端执行Lua脚本,一旦脚本开始执行,就不会被任何其他命令打断,具有绝对的原子性。
在这里插入图片描述

这就像我们把所有关键操作包成一个“事务”扔给Redis执行,Redis承诺要么一次性全完成,要么一个都不做,其他客户端在这期间不能插队。

常用语法:

-- 获取值
local val = redis.call('GET', KEYS[1])-- 设置值
redis.call('SET', KEYS[1], ARGV[1])-- 删除
redis.call('DEL', KEYS[1])
  • KEYS数组:代表Redis的key
  • ARGV数组:代表传入的参数

四、Lua脚本释放锁:拿锁-比锁-删锁三步走

我们可以这样写一个Lua脚本,来实现释放锁逻辑:

-- unlock.lua
-- 如果锁的值(线程ID)等于传入的线程ID,则删除锁
if (redis.call('GET', KEYS[1]) == ARGV[1]) thenreturn redis.call('DEL', KEYS[1])
end
return 0

这段脚本的执行具备原子性,确保:
拿锁 → 比锁 → 删除锁三个操作连成一体
中间不可能被打断或抢占

五、Java代码实现:调用Lua脚本

有了Lua脚本后,我们可以通过StringRedisTemplateexecute()方法来执行这个脚本。

1. Lua脚本保存

将上面的unlock.lua文件放在项目的resources/lua/目录下。

2. 加载Lua脚本

private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;static {UNLOCK_SCRIPT = new DefaultRedisScript<>();UNLOCK_SCRIPT.setLocation(new ClassPathResource("lua/unlock.lua"));UNLOCK_SCRIPT.setResultType(Long.class); // 返回值类型:DEL成功返回1,失败返回0
}

3. 释放锁的代码

@Override
public void unlock() {stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX + name), // 传入锁的key(KEYS数组)ID_PREFIX + Thread.currentThread().getId() // 传入线程ID(ARGV数组));
}

这样,我们的释放锁操作就是一个原子动作了!

虽然我们的分布式锁已经趋近“安全”,但它依然还不够“强大”。

六、还存在的问题:锁不住!

1. 锁不可重入

一个线程A获取了锁,然后调用另一个需要同样锁的函数B,无法再次获取锁,会直接死锁。因为Redis认为这把锁已经被人拿了(确实是你,但你又想拿一次)。

2. 没有重试机制

调用tryLock()只尝试一次失败就返回false,如果当前锁刚好被别人占用,就会放弃。这对一些关键业务来说代价太高。

3. 过期释放导致锁丢失

比如业务逻辑执行慢了,锁还没用完就被Redis自动释放了!这时别人就能抢锁,出现数据错乱。
解决方案:锁续期机制(像网吧上网时到了时间,自动续租锁的时间)。

在最后,我想一句,可能这些东西在很多内行人看来都是白雪,因为市面了已经有许多现成的轮子可以用了,比如说Redisson,但是我觉得再牛逼的框架也是从底层这样写出来的,如果不打好基础,光会安装车轮子又有什么用呢,迟早会被淘汰。


http://www.hkcw.cn/article/qiGuHCyZGq.shtml

相关文章

B1、进度汇报(— 25/05/31)

本文档汇总了各成员在 2025 年 5 月 11 日 ~ 5 月 31 日完成的工作。我们遇到了进度问题&#xff08;收工后需反思&#xff09;&#xff1a; 本学期第十四周&#xff08;05/19 ~ 05/25&#xff09;有相当多课程需要提交实验结果或上台展示。本学期第十六周&#xff08;06/02 ~…

BUUCTF[极客大挑战 2019]Havefun 1题解

BUUCTF[极客大挑战 2019]Havefun 1题解 题目分析解题理解代码逻辑&#xff1a;构造Payload&#xff1a; 总结 题目分析 生成靶机&#xff0c;进入网址&#xff1a; 首页几乎没有任何信息&#xff0c;公式化F12打开源码&#xff0c;发现一段被注释的源码&#xff1a; 下面我们…

常见算法题目5 -常见的排序算法

常见算法题目5 -常见的排序算法 本文介绍常见的排序算法的思路及代码实现(都是按照从小到大排列)&#xff0c;包括冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序。 1.冒泡排序 思路&#xff1a;重复遍历数组&#xff0c;依次比较相邻元素&#xff0c;若顺序错误…

3.需求分析与测试用例设计方法

设计方法 测试点 定义: 测试时需要考虑的可测试方面&#xff0c;不同公司可能称为"检查点"或其它名称特点: 是需求分析的最后一个环节&#xff0c;用于解决"测哪里"和"怎么测"的问题举例说明: 如同打架时的各种招数&#xff0c;如直接约架、设…

【PCB设计】STM32开发板——电源设计

电源稳压器&#xff08;Power Regulator&#xff09;是一种在电源电压或者负载电流发生变化的时候&#xff0c;依然能够提供稳定输出电压的元件。 一、关于LDO电路 1.引入 小灯泡实验 2.LDO原理 3.LDO芯片结构框图 PNP型三极管&#xff0c;Ube上升&#xff0c;截至&#xff…

BUUCTF[HCTF 2018]WarmUp 1题解

BUUCTF[HCTF 2018]WarmUp 1题解 分析解题过程代码审计主体函数CHECK函数&#xff1a; 构造payload 总结 分析 启动靶机&#xff0c;进入网址&#xff0c;是一张滑稽的表情包&#xff1a; 程序化F12查看源码&#xff1a; 发现注释内容&#xff0c;访问 url:/source.php得到…

如何使用DAXStudio将PowerBI与Excel连接

如何使用DAXStudio将PowerBI与Excel连接 之前分享过一篇自动化文章&#xff1a;PowerBI链接EXCEL实现自动化报表&#xff0c;使用一个EXCEL宏工作薄将PowerBI与EXCEL连接起来&#xff0c;今天分享另一个方法&#xff1a;使用DAX Studio将PowerBI与EXCEL连接。 下面是使用DAX S…

neo4j 5.19.0两种基于向量进行相似度查询的方式

介绍 主要讲的是两种相似度查询 一种是创建向量索引&#xff0c;然后直接从索引的所有数据中进行相似度搜索&#xff0c;这种不支持基于自己查询的结果中进行相似度匹配另一种是自己调用向量方法生产相似度进行相似度搜索&#xff0c;这种可以基于自己的查询结果中进行相似度…

中科院报道铁电液晶:从实验室突破到多场景应用展望

2020年的时候&#xff0c;相信很多关注科技前沿的朋友都注意到&#xff0c;中国科学院一篇报道聚焦一项有望改写显示产业格局的新技术 —— 铁电液晶&#xff08;FeLC&#xff09;。这项被业内称为 "下一代显示核心材料" 的研究&#xff0c;究竟取得了哪些实质性进展…

任务26:绘制1-12月各省份平均气温和预测可视化图形(折线

任务描述 知识点&#xff1a; DjangoECharts 重 点&#xff1a; DjangoECharts折线图 内 容&#xff1a; 绘制列表框&#xff0c;能够切换不同的省份根据ECharts官方示例&#xff0c;绘制ECharts折线图根据ECharts配置项手册&#xff0c;修改ECharts图形配置 任务指导…

【Redis】Set 集合

文章目录 常用命令saddsmemberssismemberscardspopsmovesrem 集合间操作sinter && sinterstoresunion && sunionstoresdiff && sdiffstore 内部编码应用场景 集合类型也是用于存储多个字符串类型的数据结构 集合中元素之间是 1. 无序的 2. 不允许重复的…

python打卡训练营打卡记录day43

复习日 作业&#xff1a; kaggle找到一个图像数据集&#xff0c;用cnn网络进行训练并且用grad-cam做可视化 进阶&#xff1a;并拆分成多个文件 数据集来源&#xff1a;Flowers Recognition 选择该数据集原因&#xff1a; 中等规模&#xff1a;4242张图片 - 训练快速但足够展示效…

向量空间的练习题目

1.考虑 中的向量x1 和x2 求每一向量的长度 令x3x1x2,求x3的长度&#xff0c;它的长度与x1和x2的和有什么关系&#xff1f; 2.重复练习1&#xff0c;取向量 3.令C为复数集合&#xff0c;定义C上的加法为 (abi)(cdi)(ac)(bd)i 并定义标量乘法为对所有实数a (abi) a bi 证明&…

Android Studio历史版本下载地址汇总

Android Studio 下载文件归档 | Android Developers本页提供了各个 Android Studio 版本的下载归档文件。https://developer.android.google.cn/studio/archive?hlzh-cn

SpringBoot-Thymeleaf

大佬写的真好&#xff1a;Thymeleaf一篇就够了-阿里云开发者社区

序列搜索策略

序列搜索策略 贪心搜索&#xff08;greedy search&#xff09; 在大语言模型中&#xff0c; 对于输出序列的每一时间步t′&#xff0c; 我们都将基于贪心搜索从Y中找到具有最高条件概率的词元&#xff0c;即&#xff1a; y t ′ argmax ⁡ y ∈ Y P ( y ∣ y 1 , … , y t ′…

MG影视登录解锁永久VIP会员 v8.0 支持手机电视TV版影视直播软件

MG影视登录解锁永久VIP会员 v8.0 支持手机电视TV版影视直播软件 MG影视App电视版是一款资源丰富、免费便捷、且专为大屏优化的影视聚合应用&#xff0c;聚合海量资源&#xff0c;畅享电视直播&#xff0c;是您电视盒子和…

【浏览器】无法连接到互联网解决方法

Mac网络连接一切正常&#xff08;手机连接互联网能正常使用&#xff09; 但是涉及到网络界面就提示“无法连接到互联网”&#xff1a; 解决办法&#xff1a; 点击左上角→系统设置→网络→→位置→编辑位置→→新增一个即可 正常了!!

【C语言预处理详解(下)】--#和##运算符,命名约定,命令行定义 ,#undef,条件编译,头文件的包含,嵌套文件包含,其他预处理指令

目录 五.#和##运算符 5.1--#运算符 5.2--##运算符 六.命名约定&#xff0c;#undef&#xff0c;命令行定义 6.1--命名约定 6.2--#undef 6.3--命名行定义 七.条件编译 常见的条件编译指令&#xff1a; 1.普通的条件编译&#xff1a; 2.多个分支的条件编译(可以利用条…

数据资产评估进阶:精读资产评估专家指引第9号——数据资产评估指导【附全文阅读】

这篇文档是有关数据资产评估的专业报告&#xff0c;以下是文档中需要关注的重点内容&#xff1a; 1. 评估对象&#xff1a;文档中提到了数据资产评估的评估对象&#xff0c;即被评估数据资产。需要关注被评估数据资产的信息属性、法律属性、价值属性等&#xff0c;以及其特征对…