Redis-分布式锁实现方式

在这里插入图片描述

文章目录

    • Redis分布式锁的作用?
    • Redis分布式锁的底层原理实现?
    • Redis分布式锁的应用场景?
    • Redis分布式锁遇到相关的场景问题?
      • 死锁问题
      • 锁超时问题
      • 归一问题
      • 可重入问题
      • 阻塞与非阻塞问题
      • 公平锁(Fair Lock)
      • 公平锁(Fair Lock)
    • 本篇小结

更多相关内容可查看

Redis分布式锁的作用?

Redis分布式锁是一种通过Redis实现的锁机制,用于在分布式系统中控制对共享资源的访问,以确保在同一时间只有一个客户端或进程可以对资源进行操作。以下是Redis分布式锁的主要作用:

  • 互斥访问控制: Redis分布式锁可确保在分布式环境下对共享资源的互斥访问。当多个客户端同时尝试获取锁时,只有一个客户端能成功获取,其他客户端将被阻塞或进行重试。
  • 资源保护: 分布式锁可用于保护共享资源的一致性和完整性。当多个客户端需要对某一资源执行操作时,通过获取分布式锁,确保每个操作按顺序执行,避免数据损坏或冲突。
  • 并发控制: Redis分布式锁允许对某个资源进行并发访问的控制。通过限制同一时间内只有一个客户端可以获取锁,确保对资源的并发访问不会导致竞态条件或不一致的结果。
  • 避免重复任务: 分布式锁可用于防止重复执行特定任务,特别是在定时任务或异步处理场景下。通过获取锁后执行任务,其他客户端在获取锁失败时知道任务已经在执行,避免重复执行。
  • 防止任务重入: Redis分布式锁可防止同一客户端对同一资源进行重入操作。当一个客户端已经持有锁时,其他请求同一个资源的操作将被阻塞或进行重试,确保资源只被一个客户端处理。

Redis分布式锁的底层原理实现?

Redis分布式锁主要依靠一个SETNX指令实现的 , 这条命令的含义就是“SET if Not Exists”,即不存在的时候才会设置值。只有在key不存在的情况下,将键key的值设置为value。如果key已经存在,则SETNX命令不做任何操作。

这个命令的返回值如下。

● 命令在设置成功时返回1。
● 命令在设置失败时返回0。

例:假设此时有线程A和线程B同时访问临界区代码,假设线程A首先执行了SETNX命令,并返回结果1,继续向下执行。而此时线程B再次执行SETNX命令时,返回的结果为0,则线程B不能继续向下执行。只有当线程A执行DELETE命令将设置的锁状态删除时,线程B才会成功执行SETNX命令设置加锁状态后继续向下执行

Boolean isLocked = stringRedisTemplate.opsForValue().setIfAbsent(PRODUCT_ID, "binghe");

Redis分布式锁的应用场景?

可应用于面试题

  • 参考回答一 : 在我最近做的一个项目中 , 我们在任务调度的时候使用了分布式锁 早期我们在进行定时任务的时候我们采用的是SpringTask实现的 , 在集群部署的情况下, 多个节点的定时任务会同时执行 ,造成重复调度
    , 影响运算结果, 浪费系统资源 这里为了防止这种情况的发送, 我们使用Redis实现分布式锁对任务进行调度管理
    ,防止重复任务执行,后期因为我们系统中的任务越来越多 , 执行规则也比较多 , 而且单节点执行效率有一定的限制 ,
    所以定时任务就切换成了XXL-JOB ,系统中就没有再使用分布式锁了
  • 参考回答二 : 我们项目在下单的过程中为了防止订单超卖 , 使用了分布式锁
  • 参考回答三 : 我们项目中有一个用户预约充电桩的功能, 为了避免多个用户预约到同一个充电桩, 使用可分布式锁
  • 参考回答四 : 我们项目中有一个抢座功能 , 为了避免同一个座位被多个用户购买, 使用了分布式锁

Redis分布式锁遇到相关的场景问题?

死锁问题

在使用分布式锁的时候, 如果因为一些原因导致系统宕机, 锁资源没有被释放, 就会产生死锁

解决的方案 : 上锁的时候设置锁的超时时间

Boolean isLocked = stringRedisTemplate.opsForValue().setIfAbsent(PRODUCT_ID, "binghe", 30, TimeUnit.SECONDS);

锁超时问题

如果业务执行需要的时间, 超过的锁的超时时间 , 这个时候业务还没有执行完成, 锁就已经自动被删除了,其他请求就能获取锁, 操作这个资源 , 这个时候就会出现并发问题 ,

解决的方案 :

  • 入Redis的watch dog机制, 自动为锁续期
  • 开启子线程 , 每隔20S运行一次, 重新设置锁的超时时间

归一问题

  • 如果一个线程获取了分布式锁, 但是这个线程业务没有执行完成之前 , 锁被其他的线程删掉了 , 又会出现线程并发问题 ,
  • 这个时候就需要考虑归一化问题 就是一个线程执行了加锁操作后,后续必须由这个线程执行解锁操作,加锁和解锁操作由同一个线程来完成。

解决的方案 : 为了解决只有加锁的线程才能进行相应的解锁操作的问题,那么,我们就需要将加锁和解锁操作绑定到同一个线程中,可以使用ThreadLocal来解决这个问题, 加锁的时候生成唯一标识保存到ThreadLocal , 并且设置到锁的值中 , 释放锁的时候, 判断线程中的唯一标识和锁的唯一标识是否相同, 只有相同才会释放

public class RedisLockImpl implements RedisLock{@Autowiredprivate StringRedisTemplate stringRedisTemplate;private ThreadLocal<String> threadLocal = new ThreadLocal<String>();@Overridepublic boolean tryLock(String key, long timeout, TimeUnit unit){String uuid = UUID.randomUUID().toString();threadLocal.set(uuid);return stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);}@Overridepublic void releaseLock(String key){//当前线程中绑定的uuid与Redis中的uuid相同时,再执行删除锁的操作if(threadLocal.get().equals(stringRedisTemplate.opsForValue().get(key))){stringRedisTemplate.delete(key);   }}
}

可重入问题

当一个线程成功设置了锁标志位后,其他的线程再设置锁标志位时,就会返回失败。
还有一种场景就是在一个业务中, 有个操作都需要获取到锁, 这个时候第二个操作就无法获取锁了 , 操作会失败

场景示例 :

下单业务中, 扣减商品库存会给商品加锁, 增加商品销量也需要给商品加锁 , 这个时候需要获取二次锁 第二次获取商品锁就会失败 ,这就需要我们的分布式锁能够实现可重入

解决方案 : 实现可重入锁最简单的方式就是使用计数器 , 加锁成功之后计数器 + 1 , 取消锁之后计数器 -1 , 计数器减为0 , 真正从Redis删除锁

public class RedisLockImpl implements RedisLock{@Autowiredprivate StringRedisTemplate stringRedisTemplate;private ThreadLocal<String> threadLocal = new ThreadLocal<String>();private ThreadLocal<Integer> threadLocalInteger = new ThreadLocal<Integer>();@Overridepublic boolean tryLock(String key, long timeout, TimeUnit unit){Boolean isLocked = false;if(threadLocal.get() == null){String uuid = UUID.randomUUID().toString();threadLocal.set(uuid);isLocked = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit);}else{isLocked = true;   }//加锁成功后将计数器加1if(isLocked){Integer count = threadLocalInteger.get() == null ? 0 : threadLocalInteger.get();threadLocalInteger.set(count++);}return isLocked;}@Overridepublic void releaseLock(String key){//当前线程中绑定的uuid与Redis中的uuid相同时,再执行删除锁的操作if(threadLocal.get().equals(stringRedisTemplate.opsForValue().get(key))){Integer count = threadLocalInteger.get();//计数器减为0时释放锁if(count == null || --count <= 0){stringRedisTemplate.delete(key);      }}}
}

阻塞与非阻塞问题

在使用分布式锁的时候 , 如果当前需要操作的资源已经加了锁, 这个时候会获取锁失败, 直接向用户返回失败信息 , 用户的体验非常不好 , 所以我们在实现分布式锁的时候, 我们可以将后续的请求进行阻塞,直到当前请求释放锁后,再唤醒阻塞的请求获得分布式锁来执行方法。

解决的方案 : 参考自旋锁的思想, 获取锁失败自选获取锁, 直到成功为止 , 当然为了防止多条线程自旋带来的系统资料消耗, 可以设置一个自旋的超时时间 , 超过时间之后, 自动终止线程 , 返回失败信息

@0verride
public boolean tryLock(String key, long timeout, TimeUnit unit)
{Boolean isLocked = false;if(threadLocal.get()== null){String uuid = UUID.randomUUID().tostring();threadLocal.set(uuid);isLocked =stringRedisTemplate.opsForValue().setIfAbsent(key, uuid,timeout,unit):/如果获取锁失败则自旋获取锁,自到成工if(!isLocked){for(;;){isLocked = stringRedisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, unit)if(isLocked){break;}}}else{isLocked = true;}//加锁成功后将计数器加1if(isLocked){Integer count = threadLocalInteger.get()== null ?0threadLocalInteger.get();threadLocalInteger.set(count++);}return isLocked:
}

公平锁(Fair Lock)

  • 公平锁保证锁的获取按照请求的顺序进行,即先请求锁的线程先获取锁,遵循先来先服务的原则。
  • 公平锁的实现会维护一个请求队列,当锁被释放时,队列中的第一个请求线程会被唤醒并获得锁。

公平锁(Fair Lock)

  • 非公平锁在锁释放时不会考虑请求锁的顺序,它允许某个后来的请求线程在当前锁被释放时立即尝试获取锁。
  • 非公平锁的优势在于它可以减少锁竞争的开销,尤其是在高并发情况下,因为它允许某些线程在等待队列中绕过排队直接获取锁。

本篇小结

其他Redis的相关问题链接如下
Redis数据持久化策略
Redis数据过期策略
Redis数据淘汰策略
Redis集群方案
Redis主从同步
Redis分片集群如何存储及读取数据
Redis跟Mysql如何保证数据一致性
Redis的缓存穿透、缓存击穿、缓存雪崩及解决方案

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

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

相关文章

tomcat--应用部署

tomcat根目录结构 Tomcat中默认网站根目录是/usr/local/apache-tomcat-8.5.100/webapps/在Tomcat的webapps目录中&#xff0c;有个非常特殊的目录ROOT&#xff0c;它就是网站默认根目录。将eshop解压后的文件放到这个/usr/local/apache-tomcat-8.5.100/webapps/ROOT中。bbs解压…

Blender雕刻建模_笔画,镜像,动态拓扑

笔画 笔画选项&#xff0c;一般是对刷子&#xff08;自由线&#xff09;工具设置 描边方法如下&#xff1a;标红的为常用 -间隔&#xff1a;按一定间隔应用笔画的结果 例如&#xff1a;笔刷半径50&#xff0c;笔画间隔100%&#xff08;笔刷直径的百分比&#xff09;&#x…

Git项目管理——提交项目和版本回退(二)

个人名片&#xff1a; &#x1f393;作者简介&#xff1a;嵌入式领域优质创作者&#x1f310;个人主页&#xff1a;妄北y &#x1f4de;个人QQ&#xff1a;2061314755 &#x1f48c;个人邮箱&#xff1a;[mailto:2061314755qq.com] &#x1f4f1;个人微信&#xff1a;Vir2025WB…

一看就会的AOP事务

文章目录 AOPAOP简介AOP简介和作用AOP的应用场景为什么要学习AOP AOP入门案例思路分析代码实现AOP中的核心概念 AOP工作流程AOP工作流程AOP核心概念在测试类中验证代理对象 AOP切入点表达式语法格式通配符书写技巧 AOP通知类型AOP通知分类AOP通知详解 AOP案例案例-测量业务层接…

(1)双指针算法介绍与练习:移动零

目录 双指针算法介绍 练习&#xff1a;移动零 双指针算法介绍 双指针算法常见于数组和双向链表的题型 在数组中&#xff0c;双指针中的指针代表数组元素的下标&#xff0c;而不是真正的指针类型变量 在双向链表中&#xff0c;双指针中的指针即为真正意义上的指针&#xff…

线性系统(二)

线性系统&#xff08;二&#xff09; 1.直观理解线性方程组结构2. 不同解的结论3. 更一般的高斯-约旦消元法4.齐次线性方程组 链接: 线性系统&#xff08;一&#xff09; 1.直观理解线性方程组结构 长这样&#xff0c;方程就有解&#xff0c;即相交坐标。 长这样&#xff0c;…

免费思维13招之十三:种群型思维

免费思维13招之十三&#xff1a;种群型思维 免费思维的最后一个思维——族群思维 人&#xff0c;都是群居性的动物&#xff0c;在人群中的一部分人群对于另一部分人群来说&#xff0c;具有强大的吸引力。那么&#xff0c;我们就从这一点出发&#xff0c;通过对其中一部分人群进…

Online RL + IL :Policy Improvement via Imitation of Multiple Oracles

NIPS 2020 paper code 如何利用多个次优专家策略来引导智能体在线学习&#xff0c;后续有多个文章研究该设定下的RL。 Intro 论文探讨了在强化学习&#xff08;RL&#xff09;中&#xff0c;如何通过模仿多个次优策略&#xff08;称为oracle&#xff09;来提升策略性能的问题…

第25次修改留言板,修改了布局,样式和脚本分离

伤心城市 首页 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"beiwanglu" content"widthdevice-width, initial-scale1.0"><link rel"stylesheet" type&qu…

[Algorithm][回溯][字母大小写全排列][优美的排列][N皇后]详细讲解

目录 1.字母大小写全排列1.题目链接2.算法原理详解3.代码实现 2.优美的排列1.题目链接2.算法原理详解3.代码实现 3.N 皇后1.题目链接2.算法原理详解3.代码实现 1.字母大小写全排列 1.题目链接 字母大小写全排列 2.算法原理详解 本题逻辑与子集大致相同 思路一&#xff1a;每…

Windows 10无法远程桌面连接:原因及解决方案

在信息技术日益发展的今天&#xff0c;远程桌面连接已成为企业日常运维、技术支持乃至个人用户远程办公的必备工具。然而&#xff0c;有时我们可能会遇到Windows 10无法远程桌面连接的问题&#xff0c;这无疑会给我们的工作和生活带来诸多不便。 原因分析 1、远程访问未启用&a…

win10和win11使用wsl安装linux系统和docker.

1、wsl无法解析服务器的名称或地址 这是wsl无法访问raw这个地址。需要修改host. 你先访问这个地址&#xff0c;拿到IP。ip查询 查ip 网站ip查询 同ip网站查询 iP反查域名 iP查域名 同ip域名 如下图&#xff0c;可以平通&#xff0c;就可以开始安装了。默认Ubuntu. 安别的系…

OpenTelemetry agent 对 Spring Boot 应用的影响:一次 SPI 失效的调查

背景 前段时间公司领导让我排查一个关于在 JDK21 环境中使用 Spring Boot 配合一个 JDK18 新增的一个 SPI(java.net.spi.InetAddressResolverProvider) 不生效的问题。 但这个不生效的前置条件有点多&#xff1a; JDK 的版本得在 18SpringBoot3.x还在额外再配合使用 -javaagent…

僵尸网络的威胁值得关注

僵尸网络&#xff08;botnet&#xff09;是指一组受到恶意软件感染并遭到恶意用户控制的计算机。术语“僵尸网络”由“机器人&#xff08;bot&#xff09;”和“网络&#xff08;network&#xff09;”两个词组合而成&#xff0c;每台受感染设备被称为“机器人”。僵尸网络可用…

验证集的划分方法:确保机器学习模型泛化能力的关键

验证集的划分方法&#xff1a;确保机器学习模型泛化能力的关键 目录 一、验证集的作用 二、验证集的划分方法 三、注意事项 四、总结 在机器学习任务中&#xff0c;我们不仅要关注模型在训练数据上的表现&#xff0c;更重要的是模型在未见数据上的泛化能力。为了评估和提高…

线上虚拟展厅需要具备哪些技术特点?

虚拟展厅需要具备三维建模与渲染技术、虚拟现实技术、交互技术、多媒体展示技术、网络传输技术、大数据分析与反馈技术、跨平台兼容性等技术特点。这些技术特点共同构成了虚拟展厅的核心竞争力&#xff0c;使其能够为用户提供逼真、生动、互动的参观体验。 虚拟展厅的技术特点主…

Kotlin扩展函数和运算符重载

扩展函数 fun String.lettersCount():Int{var count 0for(i in this){if(i.isLetter())count}return count } fun main(){val str:String "12we"println(str.lettersCount()) } 相当于直接将方法写在类里面。函数体内可以直接使用this而不用传参。 运算符重载 …

c++AVL树的模拟实现

前面对map/multimap/set/multiset进行了简单的介绍&#xff0c;在其文档介绍中发现&#xff0c;这几个容器有个 共同点是&#xff1a;其底层都是按照二叉搜索树来实现的&#xff0c;但是二叉搜索树有其自身的缺陷&#xff0c;假如往树中 插入的元素有序或者接近有序&#xff0c…

meshlab: pymeshlab沿着椭圆赤道投影展开当前网格的几何图形并保存(geometric cylindrical unwrapping)

一、关于环境 请参考&#xff1a;pymeshlab遍历文件夹中模型、缩放并导出指定格式-CSDN博客 二、关于代码 本文所给出代码仅为参考&#xff0c;禁止转载和引用&#xff0c;仅供个人学习。 本文所给出的例子是https://download.csdn.net/download/weixin_42605076/89233917中的…

爬虫界的“闪电侠”:异步爬虫与分布式系统的实战秘籍

Hi&#xff0c;我是阿佑&#xff0c;前文给大家讲了&#xff0c;如何做一个合法“采蜜”的蜜蜂&#xff0c;有了这么个自保的能力后&#xff0c;阿佑今天就将和大家踏入 —— 异步爬虫 的大门&#xff01; 异步爬虫大法 1. 引言1.1 爬虫框架的价值&#xff1a;效率与复杂度管理…