Redis的分布式锁

目录

一、定义与原理

基于Redis的分布式锁

获取锁

释放锁

锁误删问题:因为key值一样,将别人的锁删掉了

锁误判问题二:判断锁和释放锁不是原子性的

Lua脚本


分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁

分布式锁的优点:

  • 多进程可见
  • 高可用
  • 安全性
  • 高性能
  • 互斥

知识点补充:

多进程(Multiprocessing)是操作系统中的一个重要概念,它指的是在操作系统中同时运行多个程序实例的能力。这些程序实例,即进程(Process),是系统进行资源分配和调度的一个独立单元,是程序在计算机上的一次执行活动。每个进程都有自己独立的内存空间和系统资源,它们之间通过特定的机制(如管道、消息队列、共享内存等)进行通信和数据交换。

多线程(Multithreading)是指从软件或者硬件上实现多个线程并发执行的技术。在计算机编程中,多线程是一种强大的工具,它允许程序能够同时执行多个任务,从而提高程序的执行效率和响应性。以下是对多线程的详细解释:

一、定义与原理

  • 定义:多线程是并行化的一种形式,通过拆分工作以便同时进行处理。在程序中,这些独立运行的程序片段被称作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。
  • 原理:多线程的并发执行机制原理是将一个处理器划分为若干个短的时间片,每个时间片依次轮流地执行处理各个线程,由于时间片非常短,相对于一个应用程序来说,就好像是处理器在为自己单独服务一样,从而达到多个应用程序或线程在同时进行的效果。

基于Redis的分布式锁

实现分布式锁时有两个要实现的基本方法:

获取锁

  • 互斥:确保只能有一个线程获取锁
  • 非阻塞:尝试一次,成功返回true,失败返回false
  • set lock thread ex 10 nx
  • 获取锁时存入线程标示(可以用UUID标示,UUID+ThreadId)

细节:如果第一步 setnx lock thread

第二部 expire lock 10

那么,如果redis在第一步执行完第二步执行前宕机了,就有造成死锁的概率

释放锁

  • 手动释放
  • 超时释放:获取锁时加一个超时时间
  • DEL KEY
  • 在释放锁时先获取锁中的线程标示,判断是否与当前的线程标示一致

一致释放锁,不一致不释放

锁误删问题:因为key值一样,将别人的锁删掉了

问题:线程1业务阻塞,redis锁超时已经释放,线程2拿到锁,但线程1去手动释放锁时,因为key一样,把线程2的锁给释放了,导致线程3拿到锁,同时就有两个线程拿到了锁。

解决:线程在手动释放锁的时候,判断一下这是不是自己上的锁,key的value值设为标示,UUID+ThreadId

  • ID_PREFIX 是一个静态的UUID前缀,这意味着它对于类来说是唯一的,但在多个JVM实例之间不是唯一的。不过,在这个上下文中,它主要用于与线程ID结合,以形成一个更长的、理论上唯一的标识符。
  • Thread.currentThread().getId() 获取当前线程的ID。这个ID在JVM内部是唯一的,但跨JVM则不是。

线程id:是JVM在创建线程时赋值的id,id是自增的,不同JVM的线程id可能相同

在这里锁对应的值不相同,因为值的前缀有UUID,不同的JVM不同的线程进来,都会创建锁对象上锁,每个锁对象在上锁时,都会生成一个随机的UUID作为value的前缀,所以key对应的值不同,id标示不同,就只能释放自己的锁。

未改进代码

public class SimpleRedisLock implements ILock{private String name;// 锁名称private final String KEY_PREFIX="lock:";private final String ID_PREFIX= UUID.randomUUID().toString(true)+"-";private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean tryLock(long timeoutSec) {// 给线程id加个uuid前缀,避免在不同jvm下线程id相同,导致值相同String threadId = ID_PREFIX+Thread.currentThread().getId();Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId , timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(result);}@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);}}
}

锁误判问题二:判断锁和释放锁不是原子性的

在释放锁时,线程1已经判断完了,要去释放锁,但JVM垃圾回收时,遇到了阻塞,时间过久后,锁超时释放,线程2拿到锁开始执行业务,这时,线程1恢复正常,因为已经判断过锁是我上的了,它还会去释放锁,锁的key值都是 ->lock:业务:用户id,它找到线程2上的锁,并释放,线程3就可以来拿到锁,开始执行业务

Lua脚本

保证判断锁和释放锁原子性措施

Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。Lua是一种编程语言

Redis提供的调用函数,语法如下:

redis.cail('命令名称','key','其它参数',...)

带参数的Lua语法

改进后的上锁和解锁

public class SimpleRedisLock implements ILock{private String name;// 锁名称private StringRedisTemplate stringRedisTemplate;private static final String KEY_PREFIX="lock:";// 不同jvm来加载这个类时,创建的uuid前缀不同,但这个uuid是静态的,无论对象创建多少次,都是唯一确定的private static final String ID_PREFIX= UUID.randomUUID().toString(true)+"-";private static final  DefaultRedisScript<Long> UNLOCK_SCRIPT;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}static {UNLOCK_SCRIPT = new DefaultRedisScript<>();UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));UNLOCK_SCRIPT.setResultType(Long.class);}@Overridepublic boolean tryLock(long timeoutSec) {// 给线程id加个uuid前缀,避免在不同jvm下线程id相同,导致值相同String threadId = ID_PREFIX+Thread.currentThread().getId();Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId , timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(result);}@Overridepublic void unLock() {// 执行lua脚本,来确保判断id标示和执行释放锁的命令的原子性stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX + name),ID_PREFIX+Thread.currentThread().getId());}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux驱动开发——字符设备驱动开发

1 概述 1.1 说明 本文是学习rk3568开发板驱动开发的记录&#xff0c;代码依托于rk3568开发板 1.2 字符设备介绍 字符设备是 Linux 驱动中最基本的一类设备驱动&#xff0c;字符设备就是一个一个字节&#xff0c;按照字节流进行读写操作的设备&#xff0c;读写数据是分先后顺…

Navicat Charts Creator for Mac:数据可视化利器

Navicat Charts Creator for Mac是一款专为Mac用户设计的数据可视化工具&#xff0c;它将复杂的数据转化为直观、易懂的图表&#xff0c;帮助用户更好地理解和分析数据。 该软件支持连接到多种数据库&#xff0c;如MySQL、MariaDB、PostgreSQL等&#xff0c;轻松获取实时数据&…

23 PCBEditor封装创建向导介绍24 PCBEditor3D封装展示25 PCB封装库的管理与调用

23 PCBEditor封装创建向导介绍_BGA为例&&24 PCBEditor3D封装展示&&25 PCB封装库的管理与调用 第一部分 23 PCBEditor封装创建向导介绍_BGA为例一、创建焊盘二、PCBEditor创建封装 第二部分 24 PCBEditor3D封装展示第三部分 25 PCB封装库的管理与调用一、指定库…

黑马头条vue2.0项目实战(二)——登录注册功能的实现

1. 布局结构 目标 能实现登录页面的布局 能实现基本登录功能 能掌握 Vant 中 Toast 提示组件的使用 能理解 API 请求模块的封装 能理解发送验证码的实现思路 能理解 Vant Form 实现表单验证的使用 这里主要使用到三个 Vant 组件&#xff1a; NavBar 导航栏 Form 表单 F…

Linux文件恢复

很麻烦 一般还是小心最好 特别恢复的时候 可能不能选择某个文件夹去扫描恢复 所以 删除的时候 用rm -i代替rm 一定小心 以及 探索下linux的垃圾箱机制 注意 一定要恢复到不同文件夹 省的出问题 法1 系统自带工具 debugfs 但是好像不能重启&#xff1f; testdisk 1、安装 …

C++项目——高并发内存池

一、什么是内存池 内存池(Memory Pool) 是一种动态内存分配与管理技术。 通常情况下&#xff0c;程序员习惯直接使用new、delete、malloc、free 等API申请分配和释放内存&#xff0c;这样导致的后果是&#xff1a;当程序长时间运行时&#xff0c;由于所申请内存块的大小不定&…

OpenCV 图像预处理—图像金字塔

文章目录 相关概念高斯金字塔拉普拉斯金字塔应用 构建高斯金字塔为什么要对当前层进行模糊&#xff1f;1. 平滑处理2. 减少混叠&#xff08;Aliasing&#xff09;3. 多尺度表示4. 图像降采样 举个栗子创建高斯金字塔和拉普拉斯金字塔&#xff0c;并用拉普拉斯金字塔恢复图像 相…

【VUE】个人记录:父子页面数据传递

1. 父传子 在父页面中&#xff0c;调用子页面的组件位置处&#xff0c;通过“&#xff1a;”进行参数传递 <child-component :childData"parentData"></child-component>parentData对象&#xff0c;需要在父页面的data中进行初始化声明 在子页面中&am…

百易云资产管理运营系统 comfileup.php 文件上传致RCE漏洞复现(XVE-2024-18154)

0x01 产品简介 百易云资产管理运营系统,是专门针对企业不动产资产管理和运营需求而设计的一套综合解决方案。该系统能够覆盖资产的全生命周期管理,包括资产的登记、盘点、评估、处置等多个环节,同时提供强大的运营分析功能,帮助企业优化资产配置,提升运营效率。 0x02 漏…

为RTEMS Raspberrypi4 BSP添加SPI支持

为RTEMS Raspberrypi4 BSP添加SPI支持 主要参考了dev/bsps/shared/dev/spi/cadence-spi.c RTEMS 使用了基于linux的SPI框架&#xff0c;SPI总线驱动已经在内核中实现。在这个项目中我需要实习的是 RPI4的SPI主机控制器驱动 SPI在RTEMS中的实现如图&#xff1a; 首先需要将S…

Profinet从站转TCP/IP协议转化网关(功能与配置)

如何将Profinet和TCP/IP网络连接通讯起来呢?近来几天有几个朋友问到这个问题&#xff0c;那么作者在这里统一说明一下。其实有一个不错的设备产品可以很轻易地解决这个问题&#xff0c;名为JM-DNT-PN。接下来作者就从该设备的功能及配置详细说明一下。 一&#xff0c;设备主要…

Python:随机数、随机选择的应用

step1:导入 导入的random相当于是创建了random文件里的的一个对象 import random random() 产生0~1随机数 randint(a,b)产生a~b的整数 闭区间&#xff0c;可以取到a,b random.choice(touple_name)从touple_name&#xff08;数组、列表..&#xff09;中随机选择元素 import rand…

JSP内置对象及作用域

Request 存东西ResponseSession 存东西Application [ SerlvetContext ] 存东西config [ SerlvetConfig ]out/targetpage 不用了解exception <% page contentType"text/html;charsetUTF-8" language"java" %> <html> <head><title>…

DBeaver使用SQL脚本编辑器

文章目录 1 新建脚本2 选择数据库3 编写脚本【按行执行】参考 1 新建脚本 2 选择数据库 3 编写脚本【按行执行】 光标放到需要执行的行上&#xff0c;点击【最上面的按钮】 或者选中某片代码&#xff0c;然后执行 也可以编写一个脚本然后执行 参考 dbeaver安装和使用教程 …

LeetCode 热题 HOT 100 (011/100)【宇宙最简单版】

【图论】No. 0200 岛屿数量 【中等】&#x1f449;力扣对应题目指路 希望对你有帮助呀&#xff01;&#xff01;&#x1f49c;&#x1f49c; 如有更好理解的思路&#xff0c;欢迎大家留言补充 ~ 一起加油叭 &#x1f4a6; 欢迎关注、订阅专栏 【力扣详解】谢谢你的支持&#xf…

Chrome谷歌浏览器Console(控制台)显示文件名及行数

有没有这样的困扰&#xff1f;Chrome谷歌浏览器console(控制台)不显示编译文件名及行数? 设置&#xff08;Settings&#xff09;- > 忽略列表&#xff08;lgnore List&#xff09;-> 自定义排除规则&#xff08;Custom exclusion rules&#xff09; 将自定义排除规则…

Skyeye云智能制造企业版源代码全部开放

智能制造一体化管理系统 [SpringBoot2 - 快速开发平台]&#xff0c;适用于制造业、建筑业、汽车行业、互联网、教育、政府机关等机构的管理。包含文件在线操作、工作日志、多班次考勤、CRM、ERP 进销存、项目管理、EHR、拖拽式生成问卷、日程、笔记、工作计划、行政办公、薪资模…

【数据结构】堆,优先级队列

目录 堆堆的性质大根堆的模拟实现接口实现构造方法建堆入堆判满删除判空获取堆顶元素 Java中的PriorityQueue实现的接口构造方法常用方法PriorityQueue注意事项 练习 堆 如果有一个集合K {k0&#xff0c;k1&#xff0c; k2&#xff0c;…&#xff0c;kn-1}&#xff0c;把它的…

【C++】C++入门基础

✨✨欢迎大家来到Celia的博客✨✨ &#x1f389;&#x1f389;创作不易&#xff0c;请点赞关注&#xff0c;多多支持哦&#x1f389;&#x1f389; 所属专栏&#xff1a;C 个人主页&#xff1a;Celias blog~ 目录 一、C简介 二、第一个C程序 三、namespace 命名空间 3.1 na…

UART 通信协议

文章目录 一 简介二 电平标准三 引脚定义四 数据格式五 波特率 一 简介 ​ UART (Universal Asynchronous Receiver/Transmitter)&#xff0c;通用异步收发器&#xff0c;是一种串行、异步、全双工通信协议。 串行&#xff1a;利用一条传输线&#xff0c;将数据一位一位地传送…