秒杀(四)Jmeter演示秒杀中的超卖和重复购买并解决问题

2024-05-12 19:38

本文主要是介绍秒杀(四)Jmeter演示秒杀中的超卖和重复购买并解决问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

1、超卖现象

2、重复购买现象

3、Jmeter压测演示

4、Redis解决方案


 

1、超卖现象

超卖现象大家都知道是什么,我们思考一下,为什么会超卖?

当库存接近于0的时候,在高并发的情况下会出现某时刻多个线程查询库存够的,但下一时刻某个线程秒杀成功,对库存进行减操作,使得库存变为0,照理现在的状态是不能下单成功的,因为库存已经不够了,但别的线程仍然认为数量还够,对库存进行减操作,从而导致库存出现负数的情况,那这就是超卖了。那么有小伙伴说这个问题简单,对库存加锁啊,Lock、Synchronized或者cas乐观锁,那不就解决了。加锁是一个思路,那我们再考虑一个问题,我们的秒杀服务部署是单机还是分布式呢?如果是单机的,加锁当然可以解决问题,就是可能性能会差点,并发量没有那么大,那如果是分布式部署,单机的锁已经无法解决问题了。所以我们需要换一个思路,使用redis来解决超卖的问题,以下上干货!

2、重复购买现象

同一个用户在同一时刻多次秒杀同一个商品,造成同一个用户可以购买多个秒杀商品的现象,这个问题与上面的超卖问题类似。

3、Jmeter压测演示

首先我们看下如何使用Jmeter来压测,着急的小伙伴直接通过目录跳到解决方案。

下载地址:Apache JMeter - Download Apache JMeter

下载之后解压,双击/bin/jmeter.bat,会出现dos窗口,接着会出现jmeter界面,注意不要关闭dos窗口,否则jmeter也会跟着关闭了

具体的使用方法,这里也不说了,百度一下,自行解决。下面看下配置就行了

这里的线程组配置的是1000

one-one表示同一个人多次秒杀同一件商品,many-one表示多个人多次秒杀同一件商品。

这里配置了一个同步定时器,表示的意思是依次启动1000个线程,当到达1000个线程的时候,同时去请求接口

这里可以看到redis缓存的库存数已经被减到负数了,这就是超卖的现象。。

4、Redis解决方案

方案一:使用Redis的Watch机制解决

具体的机制理解参考:https://blog.csdn.net/qq_43371004/article/details/103439599

简单来说就是:对一个键设置监听器,当没有别的线程对这个键进行操作的时候,执行操作,代码类似下面这样

public boolean preOrder(Long userId, String goodsId) {//判断库存是否足够int preStock = goodsService.getStock(goodsId);if (preStock <= 0) {return false;}//先生成预订单,分布式锁,表示用户已经购买过了,否则并发情况下会产生重复购买的情况PreOrder preOrder = new PreOrder(userId, goodsId);long result = stringRedisService.hSet(RedisConstant.PREFIX_GOODS_SECKILLING + goodsId, userId.toString(), preOrder);if (result != 1) {return false;}//如果result == 1则成功,再扣库存boolean flag = goodsService.decrStock(goodsId);//可能存在并发情况,扣减之后的库存  < 0,表示库存扣减失败if (!flag) {logger.info("秒杀失败");//删除已经创建的预订单stringRedisService.hDel(RedisConstant.PREFIX_GOODS_SECKILLING + goodsId, userId.toString());return false;}//判断库存是否小于等于0,更新标志位已售完int afterStock = goodsService.getStock(goodsId);logger.info("秒杀成功");if (afterStock <= 0) {goodsService.setSaleOver(goodsId);}return true;}

更新库存的代码使用到watch机制,如下:

    public boolean decr(String key) {Jedis jedis = null;try {jedis = jedisPool.getResource();jedis.watch(key);// watch keyString result = jedis.get(key);if (StringUtils.isEmpty(result) || Integer.parseInt(result) <= 0) {//获取值,如果为0,则返回失败return false;}Transaction tx = jedis.multi();// 开启事务tx.decr(key);return !CollectionUtils.isEmpty(tx.exec());//提交事务,如果此时key被改动了,则返回null,否则返回非空} finally {returnToPool(jedis);}}

这种方案虽然可以解决超卖问题,但是会存在问题,那就是不公平,先来的用户不一定先秒杀到,如下所示:

先来的线程都秒杀失败了,后面的反而成功了,当前如果不在意这些,使用这样方案基本可以解决超卖现象

方案二:使用Lua脚本借本

使用lua脚本解决公平性的问题,使得让先来的用户能够秒杀的商品,如果秒杀10个商品,先来的10个用户可以秒杀到商品,后面的用户没有机会秒杀到商品

代码如下,lua脚本的逻辑很容易看明白,这里不多解释,注意这里在扣减库存的同时也加入用户秒杀成功的订单,防止用户重复秒杀成功

 /*** 输入参数* 1: goodsId* 2: userId* 3: goods_seckilling_key 用户预订单key* 4: goods_stock_key 库存key* 5:stock_over_key 售完的key* 6: stock_over_value 售完标志位* 7: pre_order 预订单* 返回结果:* 0:表示已经抢光了* 1: 表示抢成功了* 2:表示已经抢过了*/private static final String decrScript ="local goods_id=KEYS[1];\r\n" +"local user_id=KEYS[2];\r\n" +"local goods_seckilling_key=KEYS[3];\r\n" +"local stock_key=KEYS[4];\r\n" +"local stock_over_key=KEYS[5];\r\n" +"local stock_over_value=KEYS[6];\r\n" +"local pre_order=KEYS[7];\r\n" +"local userExists=redis.call(\"hexists\", goods_seckilling_key, user_id);\r\n" +"if tonumber(userExists)==1 then \r\n" +"   return 2;\r\n" +"end\r\n" +"local num = redis.call(\"get\" , stock_key);\r\n" +"if tonumber(num)<=0 then \r\n" +"   redis.call(\"hset\", stock_over_key, goods_id, stock_over_value);\r\n" +"   return 0;\r\n" +"else \r\n" +"   redis.call(\"decr\", stock_key);\r\n" +"   redis.call(\"hset\", goods_seckilling_key, user_id, pre_order);\r\n" +"end\r\n" +"return 1";public String decrStockAndSavePreOrder(String goodsId, String userId, PreOrder preOrder) {return stringRedisService.execLua(decrScript, goodsId, userId, RedisConstant.PREFIX_GOODS_SECKILLING + goodsId,RedisConstant.PREFIX_GOODS_STOCK + goodsId, RedisConstant.GOODS_SALE_OVER, "1", JSON.toJSONString(preOrder)).toString();}

jmeter启动10000线程同时秒杀,效果如下:可以看到只有先来的线程能够秒杀成功,后面的线程全部显示秒杀结束,这样对所有的用户都是公平的,先来先得!!!

这篇关于秒杀(四)Jmeter演示秒杀中的超卖和重复购买并解决问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/983559

相关文章

nginx 负载均衡配置及如何解决重复登录问题

《nginx负载均衡配置及如何解决重复登录问题》文章详解Nginx源码安装与Docker部署,介绍四层/七层代理区别及负载均衡策略,通过ip_hash解决重复登录问题,对nginx负载均衡配置及如何... 目录一:源码安装:1.配置编译参数2.编译3.编译安装 二,四层代理和七层代理区别1.二者混合使用举例

MySQL中查找重复值的实现

《MySQL中查找重复值的实现》查找重复值是一项常见需求,比如在数据清理、数据分析、数据质量检查等场景下,我们常常需要找出表中某列或多列的重复值,具有一定的参考价值,感兴趣的可以了解一下... 目录技术背景实现步骤方法一:使用GROUP BY和HAVING子句方法二:仅返回重复值方法三:返回完整记录方法四:

XML重复查询一条Sql语句的解决方法

《XML重复查询一条Sql语句的解决方法》文章分析了XML重复查询与日志失效问题,指出因DTO缺少@Data注解导致日志无法格式化、空指针风险及参数穿透,进而引发性能灾难,解决方案为在Controll... 目录一、核心问题:从SQL重复执行到日志失效二、根因剖析:DTO断裂引发的级联故障三、解决方案:修复

SpringBoot+Redis防止接口重复提交问题

《SpringBoot+Redis防止接口重复提交问题》:本文主要介绍SpringBoot+Redis防止接口重复提交问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录前言实现思路代码示例测试总结前言在项目的使用使用过程中,经常会出现某些操作在短时间内频繁提交。例

C#之List集合去重复对象的实现方法

《C#之List集合去重复对象的实现方法》:本文主要介绍C#之List集合去重复对象的实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C# List集合去重复对象方法1、测试数据2、测试数据3、知识点补充总结C# List集合去重复对象方法1、测试数据

使用C#删除Excel表格中的重复行数据的代码详解

《使用C#删除Excel表格中的重复行数据的代码详解》重复行是指在Excel表格中完全相同的多行数据,删除这些重复行至关重要,因为它们不仅会干扰数据分析,还可能导致错误的决策和结论,所以本文给大家介绍... 目录简介使用工具C# 删除Excel工作表中的重复行语法工作原理实现代码C# 删除指定Excel单元

windows和Linux安装Jmeter与简单使用方式

《windows和Linux安装Jmeter与简单使用方式》:本文主要介绍windows和Linux安装Jmeter与简单使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录Windows和linux安装Jmeter与简单使用一、下载安装包二、JDK安装1.windows设

Java如何用乘号来重复字符串的功能

《Java如何用乘号来重复字符串的功能》:本文主要介绍Java使用乘号来重复字符串的功能,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java乘号来重复字符串的功能1、利用循环2、使用StringBuilder3、采用 Java 11 引入的String.rep

SQL常用操作精华之复制表、跨库查询、删除重复数据

《SQL常用操作精华之复制表、跨库查询、删除重复数据》:本文主要介绍SQL常用操作精华之复制表、跨库查询、删除重复数据,这些SQL操作涵盖了数据库开发中最常用的技术点,包括表操作、数据查询、数据管... 目录SQL常用操作精华总结表结构与数据操作高级查询技巧SQL常用操作精华总结表结构与数据操作复制表结

MySQL重复数据处理的七种高效方法

《MySQL重复数据处理的七种高效方法》你是不是也曾遇到过这样的烦恼:明明系统测试时一切正常,上线后却频频出现重复数据,大批量导数据时,总有那么几条不听话的记录导致整个事务莫名回滚,今天,我就跟大家分... 目录1. 重复数据插入问题分析1.1 问题本质1.2 常见场景图2. 基础解决方案:使用异常捕获3.