【从零开始学习Redis | 第六篇】爆改Setnx实现分布式锁
前言:
在Java后端业务中, 如果我们开启了均衡负载模式,也就是多台服务器处理前端的请求,就会产生一个问题:多台服务器就会有多个JVM,多个JVM就会导致服务器集群下的并发问题。我们在这里提出的解决思路是把锁交给Redis来实现,因为Redis是单线程的。而最基础的Redis解决集群模式下的并发问题的核心解决方案是使用Setnx构造分布式锁,下文来让我们详细的看一下过程。
目录
前言:
核心思路:
具体业务逻辑:
业务问题解决思路
1.选择加锁问题:
2.Redis分布式锁的误删问题:
3,如何保证删除锁代码的原子性?
业务杂项知识点:
1.Spring mvc中的事务失效引起的并发问题:
2.包装类与基本数据类型的差异:
总结:
核心思路:
其实整个爆改过程的思路都很清楚,我们先来解释一下SETNX的作用:
SETNX key value
SETNX命令的作用是:只有当指定的键名
key
不存在时,将键值对存储到Redis数据库中。如果键名key
已经存在,则不执行任何操作。
那么整体的核心思路就是:让当前线程尝试先创建A再执行业务逻辑代码,如果A不存在,就进行创建,并执行相关业务逻辑,业务逻辑执行完毕后释放A;如果A存在,那么说明此时有其他的线程在执行业务逻辑代码,则拒绝当前线程执行业务逻辑(挂起线程)
其实就是通过SETNX构造了一个唯一数据,并且把这个数据作为锁。这种思路使得我们的锁不再局限于某一个JAVA对象,从而避开了synchronized只能在JVM内部生效。解决了集群架构下多JVM上锁困难的困境
具体业务逻辑:
本次的具体业务应用场景是优惠卷秒杀场景,简单的来讲:就是商家发放优惠卷,用户进行抢购。而在优惠卷秒杀业务中,我们需要注意的是一人一单问题。一人一单就是一个用户只允许下一单。而我们本项目的背景是允许多端登录。我们可以想一想这个问题的核心问题:如果多端登录,在服务器集群架构的模式下,如果我们还是传统模式加锁,就会出现这个问题:
用户A同时登录的电脑和手机,在以前的模式下:我们是简单粗暴的给一人一单核心代码直接解锁。但这样做有两个问题:
1.如果直接加锁,那么也就是说程序的并发性大大降低,我们一次只能处理一个用户的优惠卷订单,效率大大降低。
2.如果是在集群模式下,传统的锁只能在一个JVM内生效,并不能跨JVM。如果用户的电脑购买优惠卷请求进入到了服务器A,而用户的手机购买优惠卷请求进入到了服务器B,那么就有可能造成优惠卷超卖的情况。
总结一下优惠卷超卖场景的业务逻辑
- 查询优惠卷是否存在
- 查询优惠卷是否在售卖时间
- 查询当前优惠卷是否还有库存
- 查询用户是否已经下过单(如果有直接返回给前端Result,封装消息类)
- 扣减优惠卷库存
- 创建订单ID
- 返回订单号给前端
- 封装订单相关信息,更新数据库
在这几步中,从4-8步就是一人一单问题,而解决优惠卷秒杀问题,大部分情况就是在解决这个问题。
业务问题解决思路
我们来一步一步看当前有哪些问题需要我们解决:
1.选择加锁问题:
在我们最开始的加锁中,我们选择的是synchronized关键字,但是它会导致程序的并发性大大降低。并且无法跨JVM容器生效。
我们为了解决synchronized关键字无法跨JVM容器生效,采用了SETNX关键字。通过这种方法,我们解决了锁跨JVM容器生效。
synchronized 是基于JVM层面的同步机制,它会锁定整个方法,而且它的作用范围限定在单个JVM内。在分布式系统或者集群环境中,synchronized 不能跨JVM工作,因此不适合作为分布式锁使用。而分布式锁 simpleRedisLock 是基于Redis实现的,可以跨多个应用实例工作,适用于分布式系统。
但是它本质上和synchronized关键字的作用一样,并没有解决程序的并发性大大降低的问题。只不过以前我们是通过synchronized关键字拦截线程,现在是通过SETNX拦截线程。
那么让我们来逆推一下思路,加锁是为了解决两个问题:
- 同一用户在不同端多次购买的相同优惠卷的行为
- 不同用户同时购买同一优惠卷的行为。
而我们可以先来优化一下同一用户在不同端多购买的行为。按照我们之前的思路是不管三七二十一就上锁。如图所示可以理解为:
但是我们真的有这个必要嘛?我们仔细想一想:如果只是为了避免同一用户在不同端多次购买的相同优惠卷,那么我们只需要针对这个用户加锁不就好了嘛?
也就是说:现在我们设计的锁,应该是只会拦截同一个用户的多次登录,而不拦截多个用户的并发登录。如图所示可以理解为:
我们从代码层面解释一下:我们利用SETNX创建key的时候,将key设置为USERID。那么此时就会出现两种情况:
1.同一用户多端登录发送购票请求,由于SETNX创建KEY的时候是根据UserID创建的,因此只能有一个端创建key成功,实现了为同一用户加锁,避免多端登录购票。
2.不同的用户由于UserID不同,因此SETNX创建KEY的时候不会失败,也就是说不会被拦截。
也就是说:我们通过根据UserID构造key的方式,实现了为每个用户加锁,提高了程序的并发性能。
我们再来解决一下:多个用户同时购买同一优惠卷的问题。我们再来转变一下角度:之所以要处理多个用户同时购买同一优惠卷,是因为会存在超卖问题。而我们如何除了加锁之外,还有没有其他的方法解决超卖问题呢?
答案是有的.我们在每一次扣减库存的时候,都同步判断一下当前数据库中优惠卷库存是否大于0不就好了嘛!
当然,这里要保证判断库存和扣减库存的原子性,不可以被打断。
其实这里的思路就是CAS算法,即Compare And Swap
那么选择加锁问题我们已经解决了,为了优化普通模式下加锁的无法跨JVM容器和拷打并发性的问题,我们采用了以下两个步骤:
- 无法跨容器:使用Redis中的SETNX来保证锁可跨JVM容器
- 并发性差:利用userID构造每个用户专属的锁,并且通过数据库操作维护多用户下单超卖问题。
此时我们用流程图来展示一下当前的执行逻辑:
当然了,为了避免死锁的出现,我们要为SETNX构造出的键值对设置过期时间,防止死锁的出现。
而接下来的问题也就是我们要着重介绍的一个问题:
2.Redis分布式锁的误删问题:
此处我们说的是同一用户多端登录引发的并发性问题,而不同用户之间由于构造的时候key就不一样,因此不存在误删问题。
在我们前面构造的业务逻辑中,理想的状态应该是:
在理想状态下,多段登录可以正确的创建和释放锁,维护程序的并发性,而在我们的业务逻辑中,可能会出现如下异常情况:
这段异常简单的来讲:线程1的阻塞使得线程1所创建的用户锁被超时释放,此时Redis中并没有针对当前用户的锁,当前用户再发起一个线程2,线程2获取到锁。而线程1此时阻塞结束,开始执行业务和最后删除锁的操作,导致线程2创建的当前用户锁被删除。此时线程2在执行自己的业务,但是整个redis中已经无针对当前用户的锁了。线程3此时尝试获取锁,获取成功。那么在这种环境下,线程1,2,3都获取到了锁并且执行了买票业务。
这种业务场景虽然少见,但仍是我们要解决的问题。
而解决的思路也很简单:主要的思路:设置锁标识,让每个线程只能删除自己的锁
也就是说:以前我们利用SETNX创建锁的时候,是不管锁的value值的,现在为了解决锁的误删问题,我们要给value中赋值,使其成为锁标识。
我们看看代码:
创建锁:
删除锁:
但是这样就对了嘛?
其实是不对的! 这是因为我们在unlock里面执行了多条语句,可能在获取锁的标识的时候,还没来得及执行delete语句,线程就又被阻塞了,此时就又会发生我们之前说的误删问题。
3,如何保证删除锁代码的原子性?
在这里我们使用的是lua脚本。Redis提供了lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。
关于lua脚本的书写我们这里不做具体介绍,感兴趣的同学可以自学,lua是基于c语言实现的,他的语法结构很简单。
Lua 教程 (w3schools.cn)https://www.w3schools.cn/lua/index.asp
将之前的unlock中的redis操作转化为lua脚本,然后再交给redis执行。
我们来看看代码:
通过这种方式,我们就确保了多条Redis命令的原子性,解决了删除锁代码的原子性问题。
业务杂项知识点:
1.Spring mvc中的事务失效引起的并发问题:
在代码框架设计的时候,我把4-8过程单独拉出来封装了一个方法:
封装部分代码:
为了保证扣减库存的时候执行的多条SQL语句的原子性,我们加上了@Transactional注解。然后在获取锁后执行业务逻辑代码的时候调用这个方法。
但这也就是一个坑点:Spring mvc中的事务是会失效的。
在Spring框架中,声明式事务管理依赖于AOP(面向切面编程)。当我们在一个方法上使用@Transactional注解时,Spring将创建一个代理对象来包装原始的Bean。这个代理对象会在方法调用前后添加事务管理的逻辑,如开启和关闭事务,以及在发生异常时进行回滚操作。
如果直接调用同一个类中的另一个@Transactional方法,由于是内部调用,并不会经过代理对象,因此事务管理相关的逻辑不会被执行。这就是为什么通常建议将事务管理放在服务层(Service Layer),并且只通过注入的方式跨类调用事务方法,确保每次调用都能通过代理对象,从而让AOP能够正确地应用事务管理的逻辑。
如果不使用Spring AOP代理机制,那么@Transactional注解将不会生效,因为没有任何机制来拦截方法调用并应用事务的边界。这意味着即使定义了事务,也不会有实际的事务行为发生,如开始新事务、加入现有事务或在发生异常时回滚事务。
总结来说,Spring的声明式事务管理是通过AOP代理实现的,不使用AOP代理将导致事务失效。要确保事务能够正常工作,必须遵循Spring的配置和使用准则,确保通过代理对象对事务方法进行调用。
因此在调用这个方法时候,我们不能直接调用,这种方式是错误的!
而应该这么调用:
2.包装类与基本数据类型的差异:
当我们使用stringRedisTemplate来操作Redis的时候,返回值会有包装类型,例如Boolean。
但是如果我们直接这样返回的话,会出现一个问题:我们要求的返回值类型是boolean,也就是基本数据类型。虽然Boolean会有自动拆箱功能,可以自动转换为boolean,但是可能会出现空指针异常!
这是为什么呢?原因很简单:Boolean是包装类,可以存放空值,而在自动拆箱的时候空值会转变为空指针。而基本数据类型不允许存储空指针。因此直接抛出空指针异常。
总结:
经过本文的讲解,我们了解了如何利用Redis实现一个简单的分布式锁。而其实Redis就已经为我们提供了一套高性能,高可用的分布式锁:Redission。在之后的文章我也会给大家介绍如何使用Redission。
如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!
相关文章:
【从零开始学习Redis | 第六篇】爆改Setnx实现分布式锁
前言: 在Java后端业务中, 如果我们开启了均衡负载模式,也就是多台服务器处理前端的请求,就会产生一个问题:多台服务器就会有多个JVM,多个JVM就会导致服务器集群下的并发问题。我们在这里提出的解决思路是把…...
Kubernetes学习笔记-Part.05 基础环境准备
目录 Part.01 Kubernets与docker Part.02 Docker版本 Part.03 Kubernetes原理 Part.04 资源规划 Part.05 基础环境准备 Part.06 Docker安装 Part.07 Harbor搭建 Part.08 K8s环境安装 Part.09 K8s集群构建 Part.10 容器回退 第五章 基础环境准备 5.1.SSH免密登录 在master01、…...
语义分割 DeepLab V1网络学习笔记 (附代码)
论文地址:https://arxiv.org/abs/1412.7062 代码地址:GitHub - TheLegendAli/DeepLab-Context 1.是什么? DeepLab V1是一种基于VGG模型的语义分割模型,它使用了空洞卷积和全连接条件随机(CRF)来提高分割…...
java设计模式学习之【建造者模式】
文章目录 引言建造者模式简介定义与用途实现方式: 使用场景优势与劣势建造者模式在spring中的应用CD(光盘)的模拟示例UML 订单系统的模拟示例UML 代码地址 引言 建造者模式在创建复杂对象时展现出其强大的能力,特别是当这些对象需…...
Spring Boot中的RabbitMQ死信队列魔法:从异常到延迟,一网打尽【RabbitMQ实战 一】
Spring Boot中的RabbitMQ死信队列魔法:从异常到延迟,一网打尽 前言第一:基础整合实现第二:处理消息消费异常第三:实现延迟消息处理第四:优雅的消息重试机制第五:异步处理超时消息第六࿱…...
nrm : 镜像源工具npm镜像切换
nrm命令 安装nrm:npm i -g nrm 查看镜像源:nrm ls,带*号的为当前使用的源 添加新镜像:nrm add [镜像源名称] <源的URL路径> 切换镜像源:nrm use [镜像源名称] 删除一个镜像源:nrm del [镜像源名称] …...
Star 10.4k!推荐一款国产跨平台、轻量级的文本编辑器,内置代码对比功能
notepad 相信大家从学习这一行就开始用了,它是开发者/互联网行业的上班族使用率最高的一款轻量级文本编辑器。但是它只能在Windows上进行使用,而且正常来说是收费的(虽然用的是pj的)。 对于想在MacOS、Linux上想使用,…...
iOS 17.2:可以修改消息提示音了
时隔2周之后,苹果今日为开发者预览版用户推送了iOS 17.2 Beta4测试版的更新,已经注册Apple Beta版软件计划的用户只需打开设置--通用--软件更新即可在线OTA升级至最新的iOS 17.2测试版。 本次更新包大小为590M左右,内部版本号为(…...
PTA 一维数组7-3出生年(本题请你根据要求,自动填充“我出生于y年,直到x岁才遇到n个数字都不相同的年份”这句话)
以上是新浪微博中一奇葩贴:“我出生于1988年,直到25岁才遇到4个数字都不相同的年份。”也就是说,直到2013年才达到“4个数字都不相同”的要求。本题请你根据要求,自动填充“我出生于y年,直到x岁才遇到n个数字都不相同的…...
【3】基于多设计模式下的同步异步日志系统-设计模式
详细介绍设计模式 单例模式 ⼀个类只能创建⼀个对象,即单例模式,该设计模式可以保证系统中该类只有⼀个实例,并提供⼀个访问它的全局访问点,该实例被所有程序模块共享。⽐如在某个服务器程序中,该服务器的配置信息存放…...
Metasploit的使用和配置
预计更新 第一章 Metasploit的使用和配置 1.1 安装和配置Metasploit 1.2 Metasploit的基础命令和选项 1.3 高级选项和配置 第二章 渗透测试的漏洞利用和攻击方法 1.1 渗透测试中常见的漏洞类型和利用方法 1.2 Metasploit的漏洞利用模块和选项 1.3 模块编写和自定义 第三章 Me…...
测试用例的设计思路
接到提测单后要做的事情: 测试准备阶段 确认提测单内包含的文件、URL地址可以访问确认需求 (迭代目标、用户故事、用户愿望、问题反馈等)确认回归测试范围、更新测试范围、新增测试范围编写测试点思维导图,过程中有问题及时进行沟通与迭代相关人员约一个…...
HCIP——交换综合实验
一、实验拓扑图 二、实验需求 1、PC1和PC3所在接口为access,属于vlan2;PC2/4/5/6处于同一网段,其中PC2可以访问PC4/5/6;但PC4可以访问PC5,不能访问PC6 2、PC5不能访问PC6 3、PC1/3与PC2/4/5/6/不在同一网段 4、所有PC通…...
大学生如何搭建自己的网站
这篇是我在大一的时候,写过的一篇文章。 前言 作为一名大学生,我觉得搭建个人网站很有意义。 这篇博客讲述的是这个寒假,我是如何从零到搭建好个人网站的过程。我提供的主要是具体的思路,也附带了一些零零散散的细节。时间跨度…...
linux 路由表的优先级
[rootlocalhost cc]# ip rule list 0: from all lookup local 32765: from 10.0.19.24 lookup 4096 32766: from all lookup main 32767: from all lookup default 现在有 4 条路由规则,优先级是怎样的,0 代表最低优先级还是最高优先级 在 Linux 的 IP …...
毕业项目分享
大家好,今天给大家分享112个有趣的Python实战项目,可以直接拿来实战练习,涵盖机器学习、爬虫、数据分析、数据可视化、大数据等内容,建议关注、收藏。 项目名称 主要技术 2023招聘数据分析可视化系统爬虫 7种薪资预测模型 Flas…...
Android启动系列之进程杀手--lmkd
本文概要 这是Android系统启动的第三篇文章,本文以自述的方式来讲解lmkd进程,通过本文您将了解到lmkd进程在安卓系统中存在的意义,以及它是如何杀进程的。(文中的代码是基于android13) 我是谁 init:“大…...
tex中的边框
文章目录 利用tcolorbox宏包给公式加框 利用tcolorbox宏包 tcolorbox可以创建一个盒子的环境,例如: \documentclass{article} \usepackage{tcolorbox} \begin{document}\begin{tcolorbox}[left1cm, right1cm, top0.5cm, bottom0.5cm,colbackblue!10!wh…...
面试题库之JAVA基础篇(三)
final 被final修饰的类不可以被继承。被final修改的方法不可以被重写。被final修改的方法,jvm会尝试内联,以提高运行效率。被final修改的变量不可变,如果修改的是引用,那么引用不可变,引用指向的对象内容可变。被fin…...
CTF-虚拟机-QEMU-前置知识-操作流程与源码阅读
文章目录 总览内存PCI设备PCI配置空间前64个字节对应源码Memorry空间的BARIO空间的BAR MMIOPMIOIspci访问PCI设备配置空间中的Memory空间和IO空间MMIOPMIO QQM(qemu object model)简洁概要将 TypeInfo 注册 TypeImpl:ObjectClass的初始化&…...
java成神秘籍第一卷
前言 适合还没有入行小白学习,有些朋友会跑来问我这行的一些问题,下面算是详细系统的整理了一下啦。 全当是学习 复盘 整理 记录了 java成神秘籍第一卷 前言一 前提1 要不要考公,考编,考研2 语言选择3 就业岗位4 目标5 考不考虑…...
golang实现文件上传(高并发+分块+断点续传+加密)
运行视频 // todo 根据前端传递文件加密 func (s *FileProcess) FileProcessEncryptionByFront(file multipart.File, h *multipart.FileHeader) interface{} { //根据字节直接处理文件 这个是前端传递的二进制流s.FileProcessInit() //文件初始化 设置原来文件…...
用HeidiSQL在MySQL中新建用户
用HeidiSQL登录到MySQL数据库,注意登录的时候要使用有权限的用户: 选择工具-》用户管理: 点击左上角的“添加”: 输入用户名、密码,并且分配权限: 点击右边的“添加对象”: 可以根据自己…...
【IPv6】IPv6协议
一、IPv6数据报格式 这是与v4报头的对比 1.8bit的版本保留了,v4版本就是4,v6就是6。 2.v6去除了v4的首部长度字段,因为v6的首部长是固定的40字节。 3.服务类型(Type of Service, ToS)和通信类型(Traffi…...
无需服务器,无需魔法,拥有一个微信机器人就是这么简单
前情提要 还没看过的朋友可以看一下上一篇文章《拥有一个微信机器人总共需要几步?》在这篇文章里,我们提到,创建微信机器人需要一个大前提--你得有一台服务器。现在,不再需要了!没错,上一篇提到的Serverles…...
1、命名空间、C++的复合类型、缺省参数
命名空间 1、命名空间的定义 使用namespace定义,使用作用域限定符::访问 #include <iostream> namespace ICBC{int money 0;void save( int m){money m;} } int main( void ){ICBC::save( 100); std::cout << "工行卡余额:"…...
colab notebook导出为PDF
目录 方法一:使用浏览器打印功能 方法二:使用nbconvert转换 方法三:在线转换 方法一:使用浏览器打印功能 一般快捷键是CTRLP 然后改变目标打印机为另存为PDF 这样就可以将notebook保存为PDF了 方法二:使用nbconver…...
【Python动漫系列】名侦探柯南(完整代码)
文章目录 名侦探柯南环境需求完整代码程序分析系列文章名侦探柯南 《名侦探柯南》是由青山刚昌创作的一部侦探漫画,于1994年开始连载,并被改编为动画、电影、游戏等多种形式。故事讲述了高中生侦探工藤新一在破案时被不良组织所毒害,身体缩小成了一个小学生,为了寻找解药并…...
【matlab】QR分解
QR分解 给定一个mn的矩阵A,其中m≥n,即矩阵A是高矩阵或者是方阵,QR分解将矩阵A分解为两个矩阵Q和R的乘积,其中矩阵Q是一个mn的各列正交的矩阵,即QTQI,矩阵R是一个nn的上三角矩阵,其对角线元素为…...
Liunx系统使用超详细(三)
本篇内容开始逐渐描述有关liunx的各种命令的使用方法! 目录 一、目录和文件区别 1.1目录: 1.2文件: 1.3总结: 二、Linux命令的写法 三、linux命令清屏 四、pwd命令 五、ls命令 5.1 ls: 5.2 ls -l:…...
公司网站建设设计公司排名/数据分析平台
截取第三个/后的字符串方法://先使用spilt按/分割符 分割字符串 成数组aa"/a1Wsm6EWNcQ/${deviceName}/user/topicname2";var bb new Array();bb aa.split("/");var ccbb.slice(3);console.log(cc); //cc 为["user", "topicnam…...
常州做网站/预测2025年网络营销的发展
本文章著作权归Pushy所有,如需转载请联系作者,并注明出处:pushy.site 1. Protobuf 1.1 介绍 Google Protocol Buffer( 简称 Protobuf) 是 Google公司研发的一种灵活高效的可序列化的数据协议。什么是序列化呢? 序列化(Serializati…...
河南建设监理协会官方网站/怎么查询搜索关键词
摘要: Linux shell/terminal 命令非常强大即使一个简单的命令就可能导致文件夹、文件或者路径文件夹等被删除。 在一些情况下Linux 甚至不会询问你而直接执行命令导致你丢失各种数据信息。 一般来说在 Web 上推荐新的 Linux 用户执行这些命令当然也有人哪些写过这代…...
荆门做网站公司/淘宝seo搜索优化工具
上一讲 数据结构之线性结构 主要讲数组与链表。这期介绍数据结构中线性结构代表栈与队列,两者通过数组与链表构造出来。栈实际应用递归,计算机函数执行调用,数学问题如: 8皇后问题 , 汉诺塔, 阶乘问题, 迷宫问题等。队列实际应用消息中间件(如…...
工作指令/广州排前三的seo公司
在本文中,读者将找到用于测试自动化的前 5 个 Python 框架的诚实比较。发现它们的所有优点和缺点 在2018年被评为最佳编程语言之后,Python仍然继续上升,根据Tiobe发布的指数,目前排名第三,仅次于Java和C。随着这种语言…...
多多在线免费观看电视剧/搜索引擎优化的方法包括
简介 PSO(粒子群算法)是群智能算法的一种,其他的群智能算法还有蚁群算法,遗传算法等。其他的智能算法还有模拟退火。之前看过一段时间的PSO,商务智能课程最后的大作业便想用一下,刚好在github上看到有人用模拟退火解决TSP…...