Reids实战——分布式锁优化(Lua脚本)

news2024/11/15 8:38:56

1 基于Redis分布式锁的问题

先来看看之前分布式锁的实现。

这个基于Redis的分布式锁仍然有着一个问题,那就是误删锁的问题。 

简单的来说,就是当第一个线程,也就是线程1,拿到锁后,但由于本身业务复杂,而导致了阻塞,超过了锁设置的超时时间,锁自动释放。这个时候,线程2进来了,也拿到了锁,但是就在线程2执行业务的途中,线程1业务完成,主动释放了锁,又因为我们释放锁的逻辑是直接删除key,这就导致了线程2的锁被误删

 

这就导致了线程安全的问题。

解决方法:在每个线程要释放锁的时候,主动判断reids中存入的线程标识,来判断是不是自己的锁,如果不是,就不能删除。

代码实现:

package com.hmdp.utils;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.UUID;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * @Author 华子
 * @Date 2022/12/3 20:53
 * @Version 1.0
 */
public class SimpleRedisLock implements ILock {

    //Redis
    private StringRedisTemplate stringRedisTemplate;
    //业务名称,也就是锁的名称
    private String name;
    public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }

    //key的前缀
    private static final String KEY_PREFIX = "lock:";
    //线程标识的前缀
    private static final String ID_PREFIX = UUID.randomUUID().toString(true);


    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程id,当作set的value
        String threadId = Thread.currentThread().getId()+ID_PREFIX;

        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }


    //释放锁
    @Override
    public void unlock() {
        String threadId = Thread.currentThread().getId()+ID_PREFIX;
        String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
        if (threadId.equals(id)){
            //删除key
            stringRedisTemplate.delete(KEY_PREFIX+name);
        }
    }
}

 2 Lua脚本的使用

 上述代码仍然会在极端的情况下仍然会出现误删锁的问题。

试想一下这种情况:

在线程判断完线程标识时,发现是自己的id,就在准备释放锁的时候,发生了阻塞。然后就是锁超时时间到了,别的线程有获取到了锁,又出现了误删的问题。

那么,又该如何解决这个问题呢?

我们不妨这样想想:

就是我们把判断id和删除id整合成一个代码,让他一次执行,不用分成两次,这样不就好了?

这里就要使用我们Redis中的脚本:Lua 。

Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。Lua是一种编程语言,它的基本语法大家可以参考网站:Lua 教程 | 菜鸟教程

这里重点介绍Redis提供的调用函数,语法如下:

例如,我们要执行set name jack,则脚本是这样:

 -- 执行reids命令
redis.call('set','name','jack')

使用redis来运行脚本

例如,我们要使用Redis调用脚本来执行set name jack,则脚本是这样:

EVAL "return redis.call('set', 'name', 'jack')"  0

注:后面的0是参数个数。

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

EVAL "return redis.call('set',KEYS[1],ARGV[1])" 1 name  Rose

注:我们实际的业务就是需要这种,参数不能写死。

接下来,就是用Lua脚本编写代码逻辑

代码 :

-- 锁的key
-- local key = KEYS[1]

-- 线程标识
-- local threadId = ARGV[1];

--获取锁中的线程标识,get key
-- local id = redis.call('get',KEYS[1]);

--具体脚本
if(redis.call('get',KEYS[1]) == ARGV[1]) then
    --释放锁 del key
    return redis.call('del',KEYS[1])
end 
return 0

然后在IDEA中编写此代码,并使用Redis调用。(注:编写Lua代码需下载EmmyLua插件)

具体实现代码:(主要看释放锁代码)

package com.hmdp.utils;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.UUID;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * @Author 华子
 * @Date 2022/12/3 20:53
 * @Version 1.0
 */
public class SimpleRedisLock implements ILock {

    //Redis
    private StringRedisTemplate stringRedisTemplate;
    //业务名称,也就是锁的名称
    private String name;
    public SimpleRedisLock(StringRedisTemplate stringRedisTemplate, String name) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.name = name;
    }

    //key的前缀
    private static final String KEY_PREFIX = "lock:";
    //线程标识的前缀
    private static final String ID_PREFIX = UUID.randomUUID().toString(true);

    //获取lua脚本
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
    static {
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
        UNLOCK_SCRIPT.setResultType(Long.class);
    }

    @Override
    public boolean tryLock(long timeoutSec) {
        //获取线程id,当作set的value
        String threadId = Thread.currentThread().getId()+ID_PREFIX;

        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }

    @Override
    public void unlock() {
        //调用Lua脚本
        stringRedisTemplate.execute(
                UNLOCK_SCRIPT,
                Collections.singletonList(KEY_PREFIX + name),
                Thread.currentThread().getId()+ID_PREFIX
        );
    }


}

 主要还是使用了StringRedisTemplate中的excute方法,这里传一个脚本参数,一个key参数,一个value参数

1. 脚本参数:使用DefaultRedisScript 进行获取脚本,需要传入脚本文件的路径,调用new ClassPathResource("unlock.lua") 当做脚本文件路径

2. key参数,就是我们的KEYS[1],是一个集合,我们把我们使用的锁的key转成一个集合当成key参数

3. value,就是我们的线程标识。

这样,我们就使用的Lua脚本将原来两步需要实现的释放锁,合成一行代码实现,就不会出现问题啦~ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/67435.html

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

相关文章

gvim写verilog环境搭建——将文本编辑器客制化定义为你自己的IDE

本文将详细描述将vim变成写Verilog代码的IDE客制化的实现过程&#xff0c;同时最后提供自己的环境。实现的过程中有使用自己写的&#xff0c;也有参考借鉴其他同学进行一定的修改&#xff0c;也有直接使用插件实现的相关功能。对应的功能实现部分均会给出相应的参考与插件地址等…

基于jsp+ssm的网上图书商城-计算机毕业设计

项目介绍 本系统的用户群体分为管理员、会员两类。不同的身份拥有不同的职责和权限。管理员的职能包括&#xff1a;图书管理、会员管理、订单信息审核、个人信息维护等 该网上书城系统应该具备有图书添加、图书信息修改、用户浏览图书、用户搜索图书、图书购买和订购等功能&a…

为什么在高速PCB设计当中信号线不能多次换孔

大家在进行PCB设计时过孔肯定是要接触的&#xff0c;那么大家知道过孔对于我们PCB的信号质量影响有多大吗&#xff1f; 在搞清楚上面这个这个问题之前我们先给大家介绍一下我们在PCB设计时过孔应该如何选取。 一般过孔种类有以下三种可以进行选择&#xff1a;&#xff08;单位是…

00后会不会改变软件测试行业现状?

不可置否&#xff0c;从年开始&#xff0c;00后开始进入职场&#xff0c;开始他们漫长的职业生涯啦。 作为经历过90后改变职场规则的80后的老人们&#xff0c;又要瑟瑟发抖啦。 因为在90后之后&#xff0c;00后又开始整顿职场了。 80后&#xff0c;上有老下有小&#xff0c;为…

纪念DedeCMS创始人IT柏拉图先生

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; IT柏拉图开发了DedeCMS&#xff0c;造福了千万站长&#xff0c;但却没有因为这套系统过上体面的生活。 &#xff08;图片取自IT柏拉图的新浪微博&#xff09; 1979年你出生了&#xff0c;比我大…

【刷题心得】双指针法|HashSet<T>

题目 1 概念梳理 1.1 ISet<T> HashSet<T> 包含不重复元素的集合称为“集set”。.NET Framework包含两个集HashSet<T>和SortedSet<T>&#xff0c;其中HashSet<T>包含不重复元素的无序列表&#xff0c;SortedSet<T>包含不重复元素的有序列…

数据科学必备Python编程基础

前言 对于Python复杂的编程语言中提取了数据分析常用的数据处理以及数据可视化等数据分析师常用的内容&#xff0c;区别与其他的Python编程教程&#xff0c;如果是纯开发的小伙伴&#xff0c;看完本系列的文章仅仅只能掌握数据相关处理的内容&#xff0c;并不能完全掌握开发方…

RestoreDet

来说说低分辨率图像中目标检测吧~~ 超分辨率&#xff08;SR&#xff09;等图像恢复算法是退化图像中目标检测不可或缺的预处理模块。然而&#xff0c;这些算法中的大多数假设退化是固定的并且是先验已知的。 论文地址&#xff1a;https://arxiv.org/pdf/2201.02314.pdf 当真…

四、文件管理(三)文件系统

目录 3.1文件系统结构 3.2文件系统布局 3.2.1文件系统在外存中的结构 3.2.2文件系统在内存中的结构 3.3外存空闲空间管理 3.3.1空闲表法 3.3.2空闲链表法 3.3.3位示图法 3.3.4成组链接法 3.4虚拟文件系统 3.5文件系统挂载(mounting) 3.1文件系统结构 从不同的角度出…

柚子是肝病“催化剂”吗?医生:滋养肝细胞,5种水果或可常吃

人体就像是一台大型的机器&#xff0c;五脏六腑就像是机器里的重要组成部分&#xff0c;血管是为五脏六腑输送机油的管道&#xff0c;骨骼是机器的整体框架等&#xff0c;它们彼此之间互相配合&#xff0c;机器才能够正常运转。 如果其中一个构造受损了&#xff0c;那么会影响到…

2023最新SSM计算机毕业设计选题大全(附源码+LW)之java散酒营销系统w5at6

对于计算机专业的学生最头疼的就是临近毕业时的毕业设计,对于如何选题,技术选型等这些问题,难道了大部分人,确实,还没毕业的学生对于这些问题还比较陌生,只有学习的理论知识,没有实战经验怎么能独自完成毕业设计这一系列的流程,今天我们就聊聊如何快速应对这一难题. 比较容易的…

第二证券|元宇宙赛道西北首个“吃螃蟹者”入场

元世界展开势头一浪高过一浪之际&#xff0c;地处西北的咸阳高新区也下决心参加这场比赛。 近来&#xff0c;咸阳高新区管委会发布《咸阳高新区元世界工业先行区举动方案》&#xff0c;对行将打出的元世界“先手牌”从多个维度予以规划。 值得关注的是&#xff0c;外界印象中&…

iOS视频流采集概述(AVCaptureSession)

需求&#xff1a;需要采集到视频帧数据从而可以进行一系列处理(如: 裁剪&#xff0c;旋转&#xff0c;美颜&#xff0c;特效....). 所以,必须采集到视频帧数据. 阅读前提: 使用AVFoundation框架 采集音视频帧数据 Overview AVCaptureSession:使用相机或麦克风实时采集音视频…

Java编程实战12:解数独

目录解数独题目示例 1提示解答解题思路完整代码解数独 题目 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能…

1553_AURIX_TC275_CCU寄存器以及模块的独立时钟生成

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 这里的BBB时钟应该是前面看到的调试时钟&#xff0c;BBB比较醒目&#xff0c;不过暂且不知道这个缩写是什么词语的缩写。 这个寄存器是CPU0的时钟设置寄存器。我有点好奇&#xff0c;这个寄…

多线程并发、线程池、同步方法、同步代码块(锁)

多线程&#xff1a; 同时执行多个程序 多线程需要硬件支持&#xff08;cpu&#xff09; 并行&#xff1a;在同一时刻&#xff0c;有多个指令在多个cpu上同时执行 并发&#xff1a;在同一时刻&#xff0c;有多个指令在单个cpu上交替执行 进程&#xff1a;正在运行的软件 独立…

详解VQVAE:Neural Discrete Representation Learning

VQVAE&#xff1a;Neural Discrete Representation Learning 原文链接&#xff1a;https://arxiv.org/abs/1711.00937 要看细节&#xff0c;强推&#xff0c;直接不用看论文了&#xff1a;VQ-VAE的简明介绍&#xff1a;量子化自编码器 - 科学空间|Scientific Spaces 一、问…

车辆管理怎么做?这六个车辆管理系统能帮到你!

通过本篇文章&#xff0c;您将了解以下问题&#xff1a;1、6个车辆管理系统盘点 2、企业使用车辆管理系统的价值是什么&#xff1f; 车辆管理系统指集合处理用车、还车、维保&#xff0c;监测油耗、车辆状态等车辆管理相关工作为一体的系统。 随着车辆数据的不断增长,如何快速…

武汉大学数据科学导论 WHU-data-science-introduction-996station GitHub鉴赏官

推荐理由&#xff1a;武汉大学数据科学导论 数据科学导论 声明: 本仓库构建目的为提供一个开源的可浏览的代码仓库,用于完成本人作业 代码作业的目录如下,纸质作业也已上传,下载地址 采用MIT协议,以供学习交流 第一次作业 决策树 第二次作业 Apriori k-means naive-bayes 第三次…

毕业设计 基于stm32与openmv的目标跟踪系统

文章目录0 前言课题简介设计框架3 硬件设计4 软件设计对被测物体的识别判断被测物体所在区域5 最后0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要求&#xff0c;这两年…