秒杀(四)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

相关文章

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

Redis 多规则限流和防重复提交方案实现小结

《Redis多规则限流和防重复提交方案实现小结》本文主要介绍了Redis多规则限流和防重复提交方案实现小结,包括使用String结构和Zset结构来记录用户IP的访问次数,具有一定的参考价值,感兴趣... 目录一:使用 String 结构记录固定时间段内某用户 IP 访问某接口的次数二:使用 Zset 进行

Spring Boot 整合 ShedLock 处理定时任务重复执行的问题小结

《SpringBoot整合ShedLock处理定时任务重复执行的问题小结》ShedLock是解决分布式系统中定时任务重复执行问题的Java库,通过在数据库中加锁,确保只有一个节点在指定时间执行... 目录前言什么是 ShedLock?ShedLock 的工作原理:定时任务重复执行China编程的问题使用 Shed

解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)

《解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)》该文章介绍了使用Redis的阻塞队列和Stream流的消息队列来优化秒杀系统的方案,通过将秒杀流程拆分为两条流水线,使用Redi... 目录Redis秒杀优化方案(阻塞队列+Stream流的消息队列)什么是消息队列?消费者组的工作方式每

关于Maven生命周期相关命令演示

《关于Maven生命周期相关命令演示》Maven的生命周期分为Clean、Default和Site三个主要阶段,每个阶段包含多个关键步骤,如清理、编译、测试、打包等,通过执行相应的Maven命令,可以... 目录1. Maven 生命周期概述1.1 Clean Lifecycle1.2 Default Li

Oracle数据库使用 listagg去重删除重复数据的方法汇总

《Oracle数据库使用listagg去重删除重复数据的方法汇总》文章介绍了在Oracle数据库中使用LISTAGG和XMLAGG函数进行字符串聚合并去重的方法,包括去重聚合、使用XML解析和CLO... 目录案例表第一种:使用wm_concat() + distinct去重聚合第二种:使用listagg,

MySQL中删除重复数据SQL的三种写法

《MySQL中删除重复数据SQL的三种写法》:本文主要介绍MySQL中删除重复数据SQL的三种写法,文中通过代码示例讲解的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下... 目录方法一:使用 left join + 子查询删除重复数据(推荐)方法二:创建临时表(需分多步执行,逻辑清晰,但会

poj2406(连续重复子串)

题意:判断串s是不是str^n,求str的最大长度。 解题思路:kmp可解,后缀数组的倍增算法超时。next[i]表示在第i位匹配失败后,自动跳转到next[i],所以1到next[n]这个串 等于 n-next[n]+1到n这个串。 代码如下; #include<iostream>#include<algorithm>#include<stdio.h>#include<math.

poj3261(可重复k次的最长子串)

题意:可重复k次的最长子串 解题思路:求所有区间[x,x+k-1]中的最小值的最大值。求sa时间复杂度Nlog(N),求最值时间复杂度N*N,但实际复杂度很低。题目数据也比较水,不然估计过不了。 代码入下: #include<iostream>#include<algorithm>#include<stdio.h>#include<math.h>#include<cstring

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监