Spring Boot中对于超卖现象的问题分析和解决方案 |
您所在的位置:网站首页 › springboot解决高并发 › Spring Boot中对于超卖现象的问题分析和解决方案 |
本文只针对单体应用的高并发导致超卖的处理方案。 超卖是指商品本来只有固定的数量比如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 |