范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

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

  本文只针对单体应用的高并发导致超卖的处理方案。
  超卖是指商品本来只有固定的数量比如10个,但是在某一时刻有大量的并发请求涌入,导致商品卖出去了比如100个,这就是超卖现象。
  本文以7种方案来实现减库存操作,然后分析每个方案有什么问题,哪个方案可以解决超卖。场景设计
  创建数据库:create database mytest charset=utf8; 复制代码
  创建一个商品表: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; 复制代码
  然后插入一条数据:INSERT INTO `mytest`.`tb_product`(`id`, `name`, `price`, `stock`) VALUES (1, "iPhone6S", 5000.00, 1); 复制代码
  现在,我们有了一个商品,且它的库存stock是1,即只有一个。JMeter模拟高并发
  JMeter可以模拟高并发场景,具体的使用请看我的这篇文章:JMeter的下载和使用
  模拟一下子进来500个请求。方案一(事务)
  先来看看一个商品减库存函数,分析在高并发下会出现的问题:/**  * 简单的减库存操作,不支持高并发  * @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) {             throw new RuntimeException("商品减库存失败");         }     } else {         throw new RuntimeException("库存不足");     } } 复制代码
  在上面的函数中,先获取该商品的信息,拿到库存数,当库存数足够,就进行减库存操作。
  但是问题是,在高并发下,会有多个线程同时读到商品的库存为1,然后就都进行了减库存操作。假如同一时刻有10个线程,那么减库存操作就会执行10次,商品库存数由1变成了-9。
  所以该方案是不行的。方案二(事务 + 方法锁)/**  * 事务 + synchronized,也不能解决高并发  * 因为AOP会在方法执行前开启事务,所以有可能在开启事务后执行方法的间隙中,有其他的线程同时开启了事务,只是概率很小,多试几次就能试出来  * 所以这种方式仍然不能解决超卖问题  * @author cc  * @date 2021-12-30 15:05 */ @Transactional(rollbackFor = Exception.class) public synchronized void syncSale(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) {             throw new RuntimeException("商品减库存失败");         }     } else {         throw new RuntimeException("库存不足");     } } 复制代码
  和方案一类似,但是在方法前面加了synchronized,经过测试方案二比方案一要好得多,但是多测几遍,会发现超卖问题依然存在,只是概率低了一些。
  这是因为Spring的AOP会在方法执行前开启事务,然后再进入加锁的方法。问题在开启事务和执行加锁方法的间隙有可能有其他线程同时开启了事务,只是这个概率比较低。
  所以这种方式仍然不能解决超卖问题。方案三(事务 + 代码块锁)/**  * 解决上面多个线程同时开启了事务的问题,将synchronized放到函数块里面  * 可以解决超卖,但是性能比较影响,并且多个请求要排队等待,不建议使用  * @author cc  * @date 2021-12-30 15:10 */ public void manualSale(Long productId) {     synchronized (this) {         sampleSale(productId);     } } 复制代码
  这种是方案二的优化版,将锁放到代码块,解决了方案二的问题。
  缺点是整个代码块都加锁,如果减库存之后还有其他的耗时操作,其他的请求就需要排很久的队。方案四(手撸SQL)
  通过这样的SQL也可以解决超卖问题:update `tb_product` set stock = stock - #{amount} WHERE id = #{productId} AND stock > 0 复制代码/**  * 手撸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) {         throw new RuntimeException("商品减库存失败");     } } 复制代码
  这是因为InnoDB引擎会自动给UPDATE、DELETE、DELETE语句添加排他锁,所以通过这样的语句可以防止超卖。
  优点很明显,简单方便。
  缺点仍然很明显,每一次都要操作数据库,对系统会造成很大的压力。
  所以在高并发这种场景下这个方案不适用。方案五(Redis缓存)
  方案四的缺点在IO,那么就用Redis在内存中处理好了。
  关于Redis可以看我的这篇文章:Spring Boot中Redis的基本使用和优雅的接口数据缓存
  使用Redis,我们要提前将商品数据缓存起来:redisTemplate.opsForHash().increment("stock", "product_1", 1); 复制代码
  缓存的方式有很多种,不一定用hash的incr,这里只是做一个示例。
  现在我们在Redis中有一个库存为1的商品,来看看代码示例:/**  * 普通的redis策略,将库存放到缓存中,不做其他处理  * 缺点:不支持高并发,会出现超卖  * @author cc  * @date 2021-12-30 14:55 */ public void redisNormal(Long productId) {     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 + synchronized)/**  * 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)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,在使用上更加灵活,所以在使用上可以看场景来决定。
  两个方案都可以解决高并发下导致的超卖问题,并且是将锁加到库存查询操作中,不影响商品下单的操作,而且使用的是内存,所以速度更快。
  作者:失败的面
  链接:https://juejin.cn/post/7047681777036427271

如今AI有多强?软体机器人能钻进身体做手术,小爱还能隔空传话其实如今也有不少机器人运用于手术领域,之前就有能做骨科手术的机器人投入使用,但今天要说的同样已经投入手术中使用的软体机器人,着实让小编大为惊叹。据悉,最近来自哈佛大学和波士顿儿童医2021年买手机别跟风,根据产品定位入手,看看你喜欢哪台?选择手机除了依赖价格和配置外,其实决定手机重大体验与优缺点的都是它们的产品定位,所以手机市场无好坏,适不适合才是最重要的,今天我们就来根据产品定位选手机,来看看定位与手机配置千丝万手机卡顿?可能存储空间不足导致,手机存储清理方法对于安卓系统,使用时间越长,系统软件运行产生垃圾越多,这时会使系统越来越卡顿,好比一个仓库货物越多,会导致可行驶的空间越小,系统好比一辆叉车,负责运输货物,空间越小导致它不得不减慢孟晚舟是如何一步步成长为父亲任正非的得力干将的?2018年,低调的华为掌舵人任正非的女儿孟晚舟在加拿大被捕,后来媒体披露是加拿大应美国要求这么做的,人们在疑惑的同时,也有更多人认识和关注到了孟晚舟这个华为创始人的千金,那么,她究开学兄弟来帮忙随印墨仓式一体机助孩子新学期天天向上孩子是社会的希望所在,也是父母一生的守候。为了孩子能够将来能够实现更好的人生价值,负责任的家长对于孩子的教育投入在力所能及的范围内都是不会吝啬的。在双减政策下,随着补习班的关停,我特立独行的马斯克,坚持让特斯拉选择视觉感知是个错误吗?无人驾驶,一个让我们伤痛也让我们心痒痒的技术。这项技术仍然在发展,但是我们却不知道它到底能不能实现,真正地从辅助驾驶迈进无人驾驶。关于无人驾驶,如果拆分开来,很多技术都值得我们去商创维电视2021秋季新品发布会定档9月23日8月25日,创维电视官微对即将发布的S系列新品进行预热。创维电视2021秋季新品发布会将会在9月23日1930举行,届时将会发布创维S系列新品。创维电视官微表示,2014年,创维电大学生买什么配置的笔记本电脑和台式电脑合适?别乱买,很有讲究时间过得真快,转眼间就快开学了,很多即将步入大学和即将读大二的学子们都考虑买电脑了,我也读过大学,同时我在大学旁经营电脑实体店已经有十几年时间了,我深知大学生使用电脑的各种需求,对你知道手机多久换一次最合适吗?看完分析,总算明白里面的门道了手机是我们生活中的必须品,方方面面都离不了它,手机也是快消品,更换手机是比较常见的。手机的更新换代越来越快,到底多久换一次手机呢?1年?2年?3年还是5年呢?其实当手机出现这些情况过来人的建议,目前这4部手机最值得选,看看你买了哪部?你用过一部手机,自然知道其各方面的表现,如果有人问你值不值得买?那肯定也是心知肚明!过来人的建议,目前这4部手机最值得选,看看你买了哪部?1realme真我Q3Pro要求不高,预算2021年,一个媒体工作者,桌面常备物品是什么?大家好,我是太空橘子。因为工作的关系,家里的书房成为了我第二个办公室,SOHO已经成为常态,今天跟大家聊聊我的桌面有什么常备好物。一MacBookPro笔记本买的时间最久,用的时间
国人要啥就给啥,宝骏RS3国际化设计,价格却便宜现如今的年轻人在选车时,会考虑能满足很多不同要求的车,比如说一流的颜值强悍的动力宽敞的空间丰富的配置,等等。但是能同时满足这些需求的车型,往往定价都比较高昂,需要预算比较宽裕的消费八万左右的SUV选哪款?颜值性能俱佳的宝骏RS3,你爱了吗如今购车的群体越来越年轻化,各车企应对市场变化也做出了一些改变,推出了更多符合年轻人审美和消费习惯的车型,宝骏RS3就是这样一款车,以青量级玩乐SUV为定位,售价区间为6。38万9三星980Pro固态即将发售,PCIe4。0加持Hello大家好我是兼容机之家的小牛。不到一个月前,三星在其官网上正式宣布了基于下一代PCIe4。0的980ProNVMe固态,现在他们已经在官网上线了介绍页面,并且一些数据已经公新买的固态硬盘一定要先进行4K对齐!不然电脑依旧卡顿Hello大家好,我是兼容机之家的小牛。如今固态硬盘几乎是人手一块了,有的玩家想要购买一块新的固态硬盘来给自己的老电脑提提速,很多玩家只知道固态硬盘读写速度会比机械硬盘快很多,但是各大厂商回应RTX系列显卡崩溃问题Hello大家好我是兼容机之家的小牛随着NVIDIA发表声明后,其各大显卡厂商也相应给出了部分回应EVGA对GeForceRTX3080系列进行回应在我们的批量生产测试中,我们发现QuadroRTX专业级图形卡,性能超越RTX3090?Hello大家好我是兼容机之家的小牛。根据消息泄露,使用GA102核心的QuadroRTX专业级图形卡的图片被曝出!图为NVIDIAQuadroRTX(安培架构),具有完整的GA1CPU架构不同也能拼性能,CinebenchR23正式发布Hello大家好,我是兼容机之家的小牛。AppleM1芯片的发布可谓是在PC界引起了一场轩然大波,这意味苹果要用自研的ARM架构的处理器正式对X86芯片的霸主地位提出挑战,在近日的八万左右的SUV选哪款?颜值性能俱佳的宝骏RS3,你爱了吗如今购车的群体越来越年轻化,各车企应对市场变化也做出了一些改变,推出了更多符合年轻人审美和消费习惯的车型,宝骏RS3就是这样一款车,以青量级玩乐SUV为定位,售价区间为6。38万9中国首家生物计算平台来了,李彦宏布局生命科技新赛道文杨剑勇本月早些时候,有消息指出百度将寻求20亿美元进军生物计算领域,消息一出备受瞩目,福布斯专栏作家杨剑勇曾在福布斯发表评论称凭借算力算法上的优势,将开创生物科技智能计算新时代,AWS领跑亚太云市场,阿里云紧随其后,腾讯位居第四文杨剑勇今年,新冠病毒疫情在全球蔓延,继而带来线上办公线上购物线上教育等需求剧增,以及企业加大对新技术投入,将各种应用服务迁移至云端,带来全球云服务市场稳健增长,尤其亚太及中国云基百度智能云登陆北上广深机场广告,输出产业智能化内功心法文杨剑勇今年,全球受到新冠病毒大流行冲击,世界经济遭遇下行。在疫情期间,为培育经济增长点,我国提出加大新型基础设施建设,以5G人工智能物联网为核心的新技术,在新基建战略背景下,各界