Spring Boot中对于超卖现象的问题分析和解决方案

您所在的位置:网站首页 springboot解决高并发 Spring Boot中对于超卖现象的问题分析和解决方案

Spring Boot中对于超卖现象的问题分析和解决方案

2024-07-14 20:44| 来源: 网络整理| 查看: 265

本文只针对单体应用的高并发导致超卖的处理方案。

超卖是指商品本来只有固定的数量比如10个,但是在某一时刻有大量的并发请求涌入,导致商品卖出去了100个,这就是超卖现象。

本文以7种方案来实现减库存操作,然后分析每个方案有什么问题,哪个方案可以解决超卖。

场景设计

创建数据库:

代码语言:javascript复制create database mytest

创建一个商品表:

代码语言:javascript复制USE mytest; DROP TABLE IF EXISTS `tb_product`; CREATE TABLE `tb_product`  (   `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',   `name` varchar(64) NOT NULL COMMENT '用户名,唯一',   `price` decimal(10, 2) NOT NULL DEFAULT 0.00 COMMENT '价格',   `stock` int(10) NOT NULL DEFAULT 0 COMMENT '库存',   PRIMARY KEY (`id`) USING BTREE, ) ENGINE = InnoDB CHARACTER SET = utf8;

然后插入一条数据:

代码语言:javascript复制INSERT INTO `mytest`.`tb_product`(`id`, `name`, `price`, `stock`) VALUES (1, 'iPhone6S', 5000.00, 1);

现在,我们有了一个商品,且它的库存stock是1,即只有一个。

JMeter模拟高并发

JMeter可以模拟高并发场景,具体的使用请看我的这篇文章:JMeter的下载和使用

模拟一下子进来500个请求。

方案一(事务)

先来看看一个商品减库存函数,分析在高并发下会出现的问题:

代码语言:javascript复制/**  * 简单的减库存操作,不支持高并发  * @author cc  * @date 2021-12-30 15:04 */ @Transactional(rollbackFor = Exception.class) public void sampleSale(Long productId) {     TbProduct product = productDao.selectByPrimaryKey(productId);     if (product == null) {         throw new RuntimeException("没有找到该商品");     }     int stock = product.getStock() - 1;     if (stock >= 0) {         product.setStock(stock);         int r = productDao.updateByPrimaryKeySelective(product);         if (r = 0) {         product.setStock(stock);         int r = productDao.updateByPrimaryKeySelective(product);         if (r = #{amount} /**  * 手撸sql的方式解决超卖问题  * InnoDB会自动给UPDATE、DELETE、DELETE语句添加排他锁  * @author cc  * @date 2021-12-30 15:03 */ public void sqlSale(Long productId) {     int amount = 1; // 要扣减的数量     int r = productDao.updateStockById(productId, amount);     if (r 0) {         redisTemplate.opsForHash().increment("stock", productKey, -1);     } else {         throw new RuntimeException("库存不足");     }     // 模拟商品下单的耗时操作     try {         Thread.sleep(2000);     } catch (Exception e) {         // 商品下单失败         System.out.println("商品下单失败");     } } 我们将商品库存的查询放到了内存中,速度更快,但是上面的代码在高并发下会出现超卖现象,所以我们要对查询操作进行加锁。方案六(Redis + synchronized)代码语言:javascript复制/**  * redis策略升级版,用synchronized给库存操作上锁  * 优点:支持高并发  * @author cc  * @date 2021-12-30 14:57 */ public void redisBySync(Long productId) {     synchronized (this) {         String productKey = "product_" + productId;         // 获取缓存中商品的库存量         int stock = Integer.parseInt(redisTemplate.opsForHash().get("stock", productKey).toString());         // 扣减库存         if (stock > 0) {             redisTemplate.opsForHash().increment("stock", productKey, -1);         } else {             throw new RuntimeException("库存不足");         }     }     // 模拟商品下单的耗时操作     try {         Thread.sleep(2000);     } catch (Exception e) {         System.out.println("商品下单失败");     } }方案七(Redis + Lock)代码语言:javascript复制private Lock lock = new ReentrantLock(); /**  * redis策略升级版,用lock给库存操作上锁  * 优点:支持高并发,使用起来比synchronized更灵活  *  * @author cc  * @date 2021-12-30 14:59 */ public void redisByLock(Long productId) {     String result = null;     lock.lock();     try {         String productKey = "product_" + productId;         // 获取缓存中商品的库存量         int stock = Integer.parseInt(redisTemplate.opsForHash().get("stock", productKey).toString());         System.out.println("stock: " + stock);         // 扣减库存         if (stock > 0) {             redisTemplate.opsForHash().increment("stock", productKey, -1);         } else {             result = "库存不足";         }     } catch (RuntimeException e) {         e.printStackTrace();     } finally {         lock.unlock();     }     if (result != null) {         throw new RuntimeException(result);     }     // 模拟商品下单的耗时操作     try {         Thread.sleep(2000);     } catch (Exception e) {         System.out.println("商品下单失败");     } }

方案六和方案七只是加锁的方式不一样,Lock比起synchronized,在使用上更加灵活,所以在使用上可以看场景来决定。

两个方案都可以解决高并发下导致的超卖问题,并且是将锁加到库存查询操作中,不影响商品下单的操作,而且使用的是内存,所以速度更快。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3