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

news2025/7/23 19:26:55

分布式锁优化(二):使用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]) then
    return 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.coloradmin.cn/o/2397870.html

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

相关文章

电脑wifi显示已禁用怎么点都无法启用

一、重启路由器与电脑 有时候&#xff0c;简单的重启可以解决很多小故障。试着先断开电源让路由器休息一会儿再接通&#xff1b;对于电脑&#xff0c;则可选择重启系统看看情况是否有改善。 二、检查驱动程序 无线网卡驱动程序的问题也是导致WiFi无法启用的常见原因之一。我…

【FPGA开发】Ubuntu16.04环境下配置Vivado2018.3—附软件包

文章目录 环境介绍关键步骤记录安装虚拟机及镜像安装vivadolicense导入 环境介绍 vivado&#xff1a;2018.3 虚拟机&#xff1a;vmware 16 pro 镜像&#xff1a;Ubuntu16.04 64位 所有相关软件压缩包&#xff1a; 链接&#xff1a;https://pan.quark.cn/s/fd2730b46b20 提取码…

vue-seamless-scroll 结束从头开始,加延时后滚动

今天遇到一个大屏需求&#xff1a; 1️⃣初始进入页面停留5秒&#xff0c;然后开始滚动 2️⃣最后一条数据出现在最后一行时候暂停5秒&#xff0c;然后返回1️⃣ 依次循环&#xff0c;发现vue-seamless-scroll的方法 ScrollEnd是监测最后一条数据消失在第一行才回调&#xff…

0-EATSA-GNN:基于图节点分类师生机制的边缘感知和两阶段注意力增强图神经网络(code)

code:https://github.com/afofanah/EATSA-GNN. 文章目录 Abstract1. Introduction1.1.动态图场景1.2.EATSA-GNN框架的背景化2. Background2.1.GNN边缘感知挑战2.2.GNN的可解释性问题2.3.EATSA-GNN可解释性3. Related worksAbstract 图神经网络(GNNs)从根本上改变了我们处理和…

配置前端控制器

一、DispatcherServlet 详解 在使用 Spring MVC 框架构建 Web 应用时&#xff0c;DispatcherServlet是整个请求处理流程的核心。本文将深入解析DispatcherServlet的作用、工作原理及其在 Spring MVC 架构中的关键地位。 1.DispatcherServlet 是什么&#xff1f; DispatcherS…

lua注意事项

感觉是lua的一大坑啊&#xff0c;它还不如函数内部就局部变量呢 注意函数等内部&#xff0c;全部给加上local得了

Git的三种合并方式

在 Gitee&#xff08;码云&#xff09;中合并分支主要有三种方式&#xff1a;​普通合并&#xff08;Merge Commit&#xff09;、压缩合并&#xff08;Squash Merge&#xff09;​和变基合并&#xff08;Rebase Merge&#xff09;​。每种方式适用于不同的场景&#xff0c;各有…

从零到一:我的技术博客导航(持续更新)

作者&#xff1a;冰茶 最后更新&#xff1a;2025年6月3日 本文收录了我的C#编程学习心得与技术探索&#xff0c;将持续更新 前言 作为一名.NET开发者&#xff0c;C#语言的学习与探索一直是我技术成长的核心路径。本文集整理了我在C#学习过程中的思考与实践&#xff0c;希望能够…

SpringBoot整合Flowable【08】- 前后端如何交互

引子 在第02篇中&#xff0c;我通过 Flowable-UI 绘制了一个简单的绩效流程&#xff0c;并在后续章节中基于这个流程演示了 Flowable 的各种API调用。然而&#xff0c;在实际业务场景中&#xff0c;如果要求前端将用户绘制的流程文件发送给后端再进行解析处理&#xff0c;这种…

【五模型时间序列预测对比】Transformer-LSTM、Transformer、CNN-LSTM、LSTM、CNN

【五模型时间序列预测对比】Transformer-LSTM、Transformer、CNN-LSTM、LSTM、CNN 目录 【五模型时间序列预测对比】Transformer-LSTM、Transformer、CNN-LSTM、LSTM、CNN预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Transformer-LSTM、Transformer、CNN-LSTM、LSTM、…

深入了解MCP基础与架构

一、引言 在人工智能技术以指数级速度渗透各行业领域的今天&#xff0c;我们正站在一个关键的技术拐点。当ChatGPT月活突破亿级、Gemini Pro实现多模态实时交互、Claude 3.5 Sonnet突破百万上下文长度&#xff0c;这些里程碑事件背后&#xff0c;一个崭新的大门逐步打开&#…

实验设计与分析(第6版,Montgomery)第5章析因设计引导5.7节思考题5.13 R语言解题

本文是实验设计与分析&#xff08;第6版&#xff0c;Montgomery著&#xff0c;傅珏生译) 第5章析因设计引导5.7节思考题5.13 R语言解题。主要涉及方差分析&#xff0c;正态假设检验&#xff0c;残差分析&#xff0c;交互作用图。 dataframe<-data.frame( yc(36,18,30,39,20…

【java面试】MySQL篇

MySQL篇 一、总体结构二、优化&#xff08;一&#xff09;定位慢查询1.1 开源工具1.2Mysql自带的慢日志查询1.3 总结 &#xff08;二&#xff09;定位后优化2.1 优化2.2 总结 &#xff08;三&#xff09;索引3.1 索引3.2 索引底层数据结构——B树3.3 总结 &#xff08;四&#…

贪心算法应用:欧拉路径(Fleury算法)详解

Java中的贪心算法应用&#xff1a;欧拉路径&#xff08;Fleury算法&#xff09;详解 一、欧拉路径与欧拉回路基础 1.1 基本概念 欧拉路径&#xff08;Eulerian Path&#xff09;是指在一个图中&#xff0c;经过图中每一条边且每一条边只经过一次的路径。如果这条路径的起点和…

【算法设计与分析】实验——二维0-1背包问题(算法分析题:算法思路),独立任务最优调度问题(算法实现题:实验过程,描述,小结)

说明&#xff1a;博主是大学生&#xff0c;有一门课是算法设计与分析&#xff0c;这是博主记录课程实验报告的内容&#xff0c;题目是老师给的&#xff0c;其他内容和代码均为原创&#xff0c;可以参考学习&#xff0c;转载和搬运需评论吱声并注明出处哦。 要求&#xff1a;3-…

【Git】View Submitted Updates——diff、show、log

在 Git 中查看更新的内容&#xff08;即工作区、暂存区或提交之间的差异&#xff09;是日常开发中的常见操作。以下是常用的命令和场景说明&#xff1a; 文章目录 1、查看工作区与暂存区的差异2、查看提交历史中的差异3、查看工作区与最新提交的差异4、查看两个提交之间的差异5…

deepseek原理和项目实战笔记2 -- deepseek核心架构

混合专家&#xff08;MoE&#xff09; ​​混合专家&#xff08;Mixture of Experts, MoE&#xff09;​​ 是一种机器学习模型架构&#xff0c;其核心思想是通过组合多个“专家”子模型&#xff08;通常为小型神经网络&#xff09;来处理不同输入&#xff0c;从而提高模型的容…

在 MATLAB 2015a 中如何调用 Python

在 MATLAB 2015a 中调用 Python 可通过系统命令调用、.NET 交互层包装、MEX 接口间接桥接、环境变量配置四种方式&#xff0c;但因该版本对 Python 支持有限&#xff0c;主要依赖的是系统命令调用与间接脚本交互。其中&#xff0c;通过 system() 函数调用 Python 脚本是最简单且…

房屋租赁系统 Java+Vue.js+SpringBoot,包括房屋类型、房屋信息、预约看房、合同信息、房屋报修、房屋评价、房主管理模块

房屋租赁系统 JavaVue.jsSpringBoot&#xff0c;包括房屋类型、房屋信息、预约看房、合同信息、房屋报修、房屋评价、房主管理模块 百度云盘链接&#xff1a;https://pan.baidu.com/s/1KmwOFzN9qogyaLQei3b6qw 密码&#xff1a;l2yn 摘 要 社会的发展和科学技术的进步&#xf…

华为OD机试真题——生成哈夫曼树(2025B卷:100分)Java/python/JavaScript/C/C++/GO六种最佳实现

2025 B卷 100分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 本文收录于专栏:《2025华为OD真题目录+全流程解析/备考攻略/经验分享》 华为OD机试真题《生成…