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

相关文章

JavaScript Array.from及其相关用法详解(示例演示)

《JavaScriptArray.from及其相关用法详解(示例演示)》Array.from方法是ES6引入的一个静态方法,用于从类数组对象或可迭代对象创建一个新的数组实例,本文将详细介绍Array... 目录一、Array.from 方法概述1. 方法介绍2. 示例演示二、结合实际场景的使用1. 初始化二

C++原地删除有序数组重复项的N种方法

《C++原地删除有序数组重复项的N种方法》给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度,不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(... 目录一、问题二、问题分析三、算法实现四、问题变体:最多保留两次五、分析和代码实现5.1、问题分析5.

Redis中如何实现商品秒杀

《Redis中如何实现商品秒杀》:本文主要介绍Redis中如何实现商品秒杀问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录技术栈功能实现步骤步骤一:准备商品库存数据步骤二:实现商品秒杀步骤三:优化Redis性能技术讲解Redis的List类型Redis的Set

WiFi6时代来临! 华三H3C NX54路由器还值得购买吗?

《WiFi6时代来临!华三H3CNX54路由器还值得购买吗?》WiFi6时代已经来临,众多路由器厂商也纷纷推出了兼容WiFi6协议的路由器,今天我们将深入体验H3CNX54路由器,这款由知名企业... 随着科技的发展,WiFi6逐渐走进了我们的日常生活之中,相比WiFi5来说,WiFi6拥有更高的带宽、更高

Jmeter如何向数据库批量插入数据

《Jmeter如何向数据库批量插入数据》:本文主要介绍Jmeter如何向数据库批量插入数据方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Jmeter向数据库批量插入数据Jmeter向mysql数据库中插入数据的入门操作接下来做一下各个元件的配置总结Jmete

使用 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