Redis高并发分布式锁

news2024/11/17 19:38:36

文章目录

  • 高并发场景秒杀抢购超卖Bug
    • 高并发场景秒杀抢购Demo
    • 测试结果
  • JVM级别锁
    • 使用nginx对本地服务进行负载均衡
  • Redis实现分布式锁
    • Redis分布式锁实现Demo
    • Redis分布式锁有关问题
  • 分布式锁性能的提升
    • 减少锁的粒度
    • 使用异步处理


高并发场景秒杀抢购超卖Bug

  在今天的数字化世界中,高并发已经成为了许多在线应用的常态,尤其是在电商平台的秒杀活动、票务系统的抢票环节,或者任何需要处理大量用户请求的场景中。然而,高并发也带来了一系列的挑战,其中最常见的就是超卖问题。

  想象一下,你正在举办一个大型的秒杀活动,数万名用户在同一时间抢购同一款限量的商品。在理想的情况下,系统应该能够正确地处理所有的请求,确保商品的库存数量不会被超额扣减。然而,现实情况往往并非如此。如果没有有效的并发控制机制,你的系统可能会在短时间内接收到大量的购买请求,导致商品的库存数量被超额扣减,即出现超卖现象。这不仅会影响到你的业务运营,也会对用户体验产生负面影响。

高并发场景秒杀抢购Demo

以下是一个高并发场景场景秒杀抢购的一个Demo:

这里我用了一个子项目继承了父项目

父pom.xml

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>3.1.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

子pom.xml

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>

下面就是代码了:

StockController.class

@RestController
@RequestMapping("/stock")
public class StockController {

    @Autowired
    StockService stockService;

    @RequestMapping("/deduct")
    public String deductStock(){
        return stockService.deductStock();
    }
}

StockService.class

@Service
public class StockService {

    @Autowired
    StringRedisTemplate stringRedisTemplate;
    public String deductStock(){
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if(stock>0){
            int realStock = stock -1;
            stringRedisTemplate.opsForValue().set("stock",realStock+"");
            System.out.println("扣减成功,剩余库存:"+realStock);
        }else {
            System.out.println("扣减失败,库存不足");
        }
        return "end";
    }
}

这里使用了redis,给redis中stock的值是200
在这里插入图片描述
然后用Apache JMeter压测工具进行压测,我这里是让500个线程在1s之内启动然后去争抢200个库存

在这里插入图片描述

为了更直观的看出到底售卖出了多少商品,我在redis中存放了一个 keyokvalue0 的值,然后修改StockService,每次售卖出一件商品的时候给redis中的ok +1。
在这里插入图片描述

在这里插入图片描述

测试结果

从控制台的输出打印可以看出出现了大量的超卖问题,同一个商品被卖出了多次,200个商品卖完,竟然卖出了313件。
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

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

JVM级别锁

学过并发,会想到使用synchronized或者ReentrantLock,这样的JVM级别的锁来解决这个问题。
在这里插入图片描述
进行压测会发现问题解决了,200件商品的确是卖出去了200次,并没有出现超卖问题。但是如果在高并发场景下,使用了分布式架构。有多个tomcat,JVM级别的锁还有用吗。

使用nginx对本地服务进行负载均衡

这里我启动了两个服务器,使用了不同端口,一个8080,一个8090
在这里插入图片描述
然后本地启动了一台虚拟机,这台虚拟机用来做本地的负载均衡使用
在这里插入图片描述
虚拟机已经安装好了nginx,首先ipconfig找到本机的局域网ip地址,在nginx.conf 配置上对该ip地址的8080端口和8090端口进行负载均衡。
在这里插入图片描述

在这里插入图片描述
最后访问虚拟机的局域网地址和nginx.conf 配置的端口号即可。虚拟机的局域网地址可以通过ifconfig命令查询
在这里插入图片描述
在这里插入图片描述
不停的访问该地址可以看到两个服务器上都有打印输出,现在对该接口进行压测发现200个商品被卖出去了245次。
在这里插入图片描述
JVM级别的锁只能管理本tomcat的线程,其他服务器的线程是没有办法管理的。那应该使用什么呢?

Redis实现分布式锁

Redis提供了一种名为SETNX的命令,可以用来实现分布式锁。
在这里插入图片描述

Redis分布式锁实现Demo

@Service
public class StockService {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    public String deductStock() {
        String lockKey = "lock:product_1";
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "root");
        if (!result) {
            return "error_code";
        }
        int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if (stock > 0) {
            int realStock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock", realStock + "");
            stringRedisTemplate.opsForValue().increment("ok");
            System.out.println("扣减成功,剩余库存:" + realStock);
        } else {
            System.out.println("扣减失败,库存不足");
        }
        stringRedisTemplate.delete(lockKey);
        return "end";
    }
}

Redis分布式锁有关问题

当然这里还有很多问题
问题1:通过SETNX上锁以后,中间代码如果出现异常,导致后面的删除锁代码无法执行,导致死锁
解决方法:添加try-catch,并将删除锁代码放在finally中,这样无论有没有抛异常都会释放锁。
在这里插入图片描述
问题2:通过SETNX上锁以后,运行过程出现 宕机 ,系统被 重启 ,同样将导致 死锁
解决方法:给锁 设置超时时间,过了一段时间以后,锁将自动释放。
在这里插入图片描述
问题3:通过SETNX上锁以后 出现异常 ,导致无法去expire设置锁的超时时间,也没有办法去手动释放锁,导致 死锁
解决方法:保证上锁和设置超时时间原子性,使用Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit);方法。

在这里插入图片描述
问题4:线程A中 上锁设置过期时间 完成以后,系统出现阻塞,导致锁已经到了过期时间并自动删除了,这时候还没执行释放锁的操作,这时候线程B上锁成功,并执行任务,结果线程A反应过来了,继续执行释放锁的操作,把线程B上的锁给释放了,后面的线程上的锁都被前面的线程释放。
解决方法:这个问题的根本点在于,线程可以释放其他线程所加的锁,可以给锁添加uuid让线程只能释放自己加的锁
在这里插入图片描述
问题5:线程A 上锁设置过期时间并执行任务后,希望判断比较锁的id之后去释放锁,判断通过以后系统出现阻塞,阻塞到 锁已经过期了,但是此时并未执行释放锁的操作,此时线程B上锁成功,并去执行任务。线程A反应了过来,然后将线程B上的锁给释放了,这时候又将出现上面的问题。
解决方法:这个问题的根本原因在于 最后判断锁id的时候和释放锁的操作没有保证 原子性 。使用redis执行LUA脚本,保证 能同时执行判断锁和释放锁。这个redis并没有提供方法。
问题6锁续命,线程A任务还没执行完,锁已经过期了,此时其他线程也会执行任务,就会出现并发安全问题。
解决方法:可以使用WatchDog,也就是给任务执行线程添加守护线程,守护线程负责对锁的expire时间进行监控,每当到过期前一秒就对过期进行判断,如果任务还在进行且锁马上过期,就对过期时间进行设置。
上面的问题5问题6都可以通过redis组件,redisson解决
在这里插入图片描述

在这里插入图片描述

分布式锁性能的提升

减少锁的粒度

上述场景可以使用分段锁。基于加锁的分段机制,可以给分布式锁的性能提升几十倍。将一个商品分成好几个key。比如一个商品1000个库存,拆成10个key,每个key放100个库存。
实际实现还是有许多细节,比如对key的轮询选择,如果一个key库存为0了,就不应该再选择这个库存了。如果每个key库存都只有1,要减少5个库存的解决。实现可以参考ConcurrentHashMap1.7的源码。

使用异步处理

你可以使用消息队列等技术,将请求的处理过程异步化。当一个请求到达时,你只需要将它放入消息队列,然后立即返回。这样,你的系统就可以快速地处理大量的并发请求。然后,你可以在后台有一个或多个工作线程,从消息队列中取出请求并处理。


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

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

相关文章

供应商索赔(金税数据)导入并创建凭证(ALV长篇备忘三)

情境/背景:供应商三包索赔款项源起QMS质量系统&#xff0c;联动金税系统完成发票开具&#xff0c;最终在SAP系统中创建完成财务凭证。该流程为手工操作&#xff0c;费时费力且效率低下容易出错。 目标/任务:把QMS供应商三包索赔业务搬上线,同SAP FI顾问梳理功能说明书&#xf…

2023-06-29:redis中什么是热点Key?该如何解决?

2023-06-29&#xff1a;redis中什么是热点Key&#xff1f;该如何解决&#xff1f; 答案2023-06-29&#xff1a; 在Redis中&#xff0c;经常被访问的key被称为热点key。 产生原因和危害 原因 热点key问题产生的原因可以归纳为以下两种情况&#xff1a; 用户对于某些数据的…

安卓弹出popup之XPopup

弹窗自己写的话。虽然很简单。但不够丝滑。如果要优雅点的。又要添加动画。但是。。。如果用上了XPopup&#xff0c;动画别人帮你写。爽不爽&#xff1f;丝滑不丝滑。。&#xff1f; 丝滑第一步。先引入依赖 implementation com.github.li-xiaojun:XPopup:2.9.19如果没有这些…

git版本回退操作

本文 git 相关命令&#xff1a; git reset&#xff1a;回退版本&#xff0c;可指定某一次提交的版本。git reset [--soft | --mixed | --hard] commitId。git revert&#xff1a;撤销某个提交&#xff0c;做反向操作&#xff0c;生成新的commitId&#xff0c;原有提交记录保留…

基于java+swing+mysql图书管理系统V7.0

基于javaswingmysql图书管理系统V7.0 一、系统介绍二、功能展示1.项目骨架2.数据库表3.项目内容4.主界面5.登陆6、借阅管理7、修改读者信息8、图书验收9、新书订购 四、其它1.其他系统实现五.获取源码 一、系统介绍 项目类型&#xff1a;Java SE项目&#xff08;awtswing&…

(四)python实战——Sqlite3数据库表的增、删、查、改操作案例

前言 Sqlite3是一个轻量级的数据库&#xff0c;本节内容我们介绍一下如何在python环境中使用Sqlite数据库&#xff0c;完成数据库表的简单增、删、查、改操作。开始本节内容之前&#xff0c;我们需要先安装好python环境&#xff0c;我们使用的是python3的环境。 正文 ①创建…

【Docker】利用Dockerfile制作个人的镜像文件详细讲解

前言 Docker 是一个开源的应用容器引擎&#xff0c;让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux或Windows操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。 &#x1f4d5;作者简介&#xff1a;热…

如何避免死锁--方法三--scoped_lock

scoped_lock是c17新增的一种模板&#xff0c;也是RAII模式。其是可变参数&#xff0c;可以接受各种互斥类型作为参数模板&#xff0c;可以指定多个互斥量。 前文中我们说到&#xff0c;lock可以锁定多个互斥量&#xff0c;scoped_lock也可以做到。 void thread1() {cout <&…

【网络互联设备】网络杂谈(15)之网桥、路由器、网关、集线器、交换机、中继器的作用与概念

涉及知识点 网桥、路由器、网关、集线器、交换机、中继器的作用与概念&#xff0c;常见的网络互联设备&#xff0c;什么是网桥、路由器、网关、集线器、交换机、中继器。 原创于&#xff1a;CSDN博主-《拄杖盲学轻声码》&#xff0c;更多内容可去其主页关注下哈&#xff0c;不…

STM32F103基于HAL工程挂载FatFS驱动SD卡实现IAP功能

STM32F103基于HAL工程挂载FatFS驱动SD卡实现IAP功能 &#x1f3ac;基于SD卡IAP升级演示&#xff1a; &#x1f4cd;相关篇《STM32F103基于HAL工程挂载FatFS驱动SD卡》 &#x1f4cc;《使用STM32F103的串口实现IAP程序升级功能》 &#x1f449;&#x1f3fb;ST相关文档&…

并发-JMM-CPU缓存一致性协议MESI

回顾 指令重排 第一V读&#xff0c;都不能指令重排&#xff1b;第二个V写&#xff0c;都不能指令重排 普通读写&#xff0c;写读都会发生指令重排&#xff0c;V写普通读写会发生指令重排&#xff0c;普通读写V读会发生指令重排 CPU缓存一致性协议MESI java—》cpu的执行过程…

Three.js教程:高光网格材质Phong

推荐&#xff1a;将 NSDT场景编辑器 加入你的3D工具链 其他系列工具&#xff1a; NSDT简石数字孪生 高光网格材质Phong 高光网格材质MeshPhongMaterial和基础网格材质MeshBasicMaterial、漫反射网格材质MeshLambertMaterial一样都是网格模型的Mesh的材质。 高光网格材质MeshPho…

Web测试的主要内容和测试方法有哪些?

Web测试的主要内容&#xff1a; 一、输入框 二、搜索功能 三、增加、修改功能 四、删除功能 五、注册、登录模块 六、上传图片测试 七、查询结果列表 八、返回键检查 九、回车键检查 十、刷新键检查 Web测试的测试方法&#xff1a; 1.在测试时&#xff0c;与网络有关的步骤或者…

npm报错(npm ERR! Unexpected token ‘.‘)

使用 nvm 将 node 切换到高版本后&#xff0c;运行 npm 相关的命令报的这个错 解决办法&#xff1a; 1、通过nvm list 命令查看当前都安装的node版本列表&#xff0c;依次通过 nvm uninstall [version] 命令将已经安装的 node 版本依次删除。 [version] 代表 node 版本号。 2…

目标检测 pytorch复现Yolov4目标检测项目

目标检测 pytorch复现Yolov4目标检测项目 YOLOV4介绍YOLOV4结构解析1、主干特征提取网络Backbone2、特征金字塔3、YoloHead利用获得到的特征进行预测4、预测结果的解码 YOLOV4的训练1、YOLOV4的改进训练技巧2、loss组成 训练自己的YoloV4模型 YOLOV4介绍 YOLOV4结构解析 1、主…

官宣了!B站将以视频播放分钟数代替播放次数

6月26日&#xff0c;哔哩哔哩&#xff08;以下简称“B站”&#xff09;迎来了14周年庆。B站董事长兼CEO陈睿进行了以“很高兴遇见你”为主题的演讲。 在B站14岁的时候&#xff0c;陈睿就演讲向大家宣布&#xff1a;为了更好地挖掘B站的优质内容&#xff0c;B站将以播放分钟数替…

pyocd打包为exe后调用弹黑窗及pyocd的api调用的问题

打包为exe的程序中调用了cmd窗口&#xff0c;调用的时候会自动弹窗&#xff0c;这个弹窗用pyinstaller的-w的方法是不行的&#xff0c;参考RT-ThreadStudio的方法是如下图写一个.bat文件&#xff0c;关闭弹窗回显 echo off cd /D %~dp0 pyocd.exe %* 但一个原因是 它是0.1.1版…

git的指令

rebase 首先在master上切出一个新分支&#xff0c;叫dev 在dev上进行开发xxx 此时master上被他人提交了东西 想把这个master提交的东西移到dev上 最后统一合并到master上 步骤&#xff1a; 1.master上进行pull&#xff0c;确保本地是最新的 2.在dev上输入git rebase master …

【综合布线设计】网络杂谈(18)深入了解综合布线系统设计

涉及知识点 什么是综合布线系统设计&#xff0c;综合布线系统设计的原则&#xff0c;工作区子系统设计&#xff0c;水平子系统设计&#xff0c;垂直子系统设计&#xff0c;管理子系统设计&#xff0c;设备间子系统设计&#xff0c;建筑群子系统设计。深入了解综合布线系统设计…

二叉树OJ题:LeetCode--226.翻转二叉树

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下LeetCode中第226道二叉树OJ题&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; 数据结构与算法专栏&#xff1a;数据结构与算法 个 人…