当前位置: 首页 > news >正文

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());}
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 漏洞复现-R24-1瑞斯康达-多业务智能网关-RCE
  • LeetCode 101.对称二叉树 C写法
  • 一款以流程管理为核心的信息化快速开发的办公OA平台,一体化工作平台,开箱即用,私活利器(附源码)
  • RK3588+MIPI+GMSL+AI摄像机:自动车载4/8通道GMSL采集/边缘计算盒解决方案
  • 深入分析程序员面试中的“八股文”到底起到了哪几个方面你的作用 有没有真正的实际作用
  • AI-PaddleOCR2.8在VS2019编译运行基于C++引擎推理CPU版本
  • 阿里P8大佬推荐的前端书籍资料,限时免费领取!
  • GraphHopper-map-navi_路径规划、导航(web前端页面版)
  • 无人机环保行业解决方案-河道自动巡检
  • Python爬虫技术 第24节 数据清洗和预处理(二)
  • 前端表格控件:打造自动化报表的高效工具
  • 国家超算互联网平台:模型服务体验与本地部署推理实践
  • 02 Golang面向对象编程_20240727 课程笔记
  • Day10--mybatis多表连接查询学习(一对一、一对多、多对多)
  • Mongodb文档和数组的通配符索引
  • [译] React v16.8: 含有Hooks的版本
  • ABAP的include关键字,Java的import, C的include和C4C ABSL 的import比较
  • Akka系列(七):Actor持久化之Akka persistence
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • canvas 高仿 Apple Watch 表盘
  • CentOS 7 修改主机名
  • ES6 学习笔记(一)let,const和解构赋值
  • Git 使用集
  • Golang-长连接-状态推送
  • Hexo+码云+git快速搭建免费的静态Blog
  • Intervention/image 图片处理扩展包的安装和使用
  • React 快速上手 - 06 容器组件、展示组件、操作组件
  • spring boot下thymeleaf全局静态变量配置
  • Spring Cloud中负载均衡器概览
  • webpack+react项目初体验——记录我的webpack环境配置
  • 初识 beanstalkd
  • 解析 Webpack中import、require、按需加载的执行过程
  • 看图轻松理解数据结构与算法系列(基于数组的栈)
  • 实战|智能家居行业移动应用性能分析
  • 我有几个粽子,和一个故事
  • 移动端唤起键盘时取消position:fixed定位
  • 用 Swift 编写面向协议的视图
  • 怎么将电脑中的声音录制成WAV格式
  • 主流的CSS水平和垂直居中技术大全
  • ​MPV,汽车产品里一个特殊品类的进化过程
  • ​如何在iOS手机上查看应用日志
  • ​软考-高级-系统架构设计师教程(清华第2版)【第12章 信息系统架构设计理论与实践(P420~465)-思维导图】​
  • ‌U盘闪一下就没了?‌如何有效恢复数据
  • #QT(一种朴素的计算器实现方法)
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (C语言)strcpy与strcpy详解,与模拟实现
  • (超简单)构建高可用网络应用:使用Nginx进行负载均衡与健康检查
  • (转)Android中使用ormlite实现持久化(一)--HelloOrmLite
  • *算法训练(leetcode)第四十五天 | 101. 孤岛的总面积、102. 沉没孤岛、103. 水流问题、104. 建造最大岛屿
  • .FileZilla的使用和主动模式被动模式介绍
  • .gitignore不生效的解决方案
  • .Net CF下精确的计时器
  • .net MVC中使用angularJs刷新页面数据列表
  • .net 调用php,php 调用.net com组件 --
  • .net 验证控件和javaScript的冲突问题