超卖问题解决方案

简介: 基于悲观锁和乐观锁解决超卖问题

我的逻辑是

  • 查询优惠券
  • 判断库存是否充足(领取数量<总数量)
  • 如果充足,更新优惠券领取数量

这里是先查询、再判断、再更新的方案,而这三个步骤都是独立的,不具备原子性。在单线程swagger测试的情况下是不会触发的

image-20230426162345530.png

但是多线程并发运行会存在下面这种情况:

image-20230426162438799.png

当线程1尚未更新时线程2就来查询,此时查到的库存数据是旧的,但是线程2又不知道就会认为库存充足,就会导致并发安全问题产生。主要原因:

  • 多线程并行运行
  • 多行代码操作共享资源,但不具备原子性
    要解决这种并发问题,大家基本都知道怎么做:加锁,锁一般可以分为:悲观锁、乐观锁

悲观锁:一种独占和排他的锁机制,保守地认为数据会被其他事务修改,所以在整个数据处理过程中将数据处于锁定状态。

乐观锁:一种较为乐观的**并发**控制方法,假设多用户并发的不会产生安全问题,因此无需独占和锁定资源。但在更新数据前,会先检查是否有其他线程修改了该数据,如果有,则认为可能有风险,会放弃修改操作

可见,悲观锁、乐观锁是对并发安全问题的处理态度不同:

  • 悲观锁认为安全问题一定会发生,所以直接独占资源。结果就是多个线程会串行执行被保护的代码。
    • 优点:安全性非常高
    • 缺点:性能较差
  • 乐观锁则认为安全问题不一定发生,所以不独占资源。结果就是允许多线程并行执行。但如果真的发生并发修改怎么办??乐观锁采用CAS(Compare And Set)思想,在更新数据前先判断数据与我之前查询到的是否一致,不一致则证明有其它线程也在更新。为了避免出现安全问题,放弃本次更新或者重新尝试一次。

乐观锁举例:

比如我们现在total_num为10,issue_num为9,也就是说还剩下1个库存了。现在有两个线程来执行修改操作。

  • 线程1、线程2都查询数据,发现total_num为10,issue_num为9
  • 线程1、线程2都判断库存是否充足,if(issue_num < total_num),发现都成立了。
  • 线程1和线程2都开始执行数据库写操作,更新issue_num。但是由于数据库的事务互斥,肯定有先有后。我们假设线程1先执行。按照乐观锁机制,在更新时要做数据检查(CAS),判断数据是否变化。因此SQL是这样:
    • UPDATE coupon SET issue_num = issue_num + 1 WHERE id = 1 AND issue_num = 9
    • 注意SQL语句结尾的AND issue_num = 9 , 这里的9就是之前查询的结果,这里就是校验是否变化,假如issue_num发生变化,此处不一致,肯定SQL就执行失败。当然线程1是第一个执行的,issue_num没有变化,所以这里会成功。因此issue_num的值+1,变为10
  • 紧接着,线程2执行,因为线程2查询的时候issue_num是9,所以线程2执行相同SQL:
    • UPDATE coupon SET issue_num = issue_num + 1 WHERE id = 1 AND issue_num = 9
    • 但线程1已经将issue_num的值更新为10,线程2的这条SQL执行时where条件不成立,执行失败,乐观锁生效了。

以上就是乐观锁的工作原理,可以发现乐观锁:

  • 优点:性能好、安全性也好
  • 缺点:并发较高时,可能出现更新成功率较低的问题(并行的N个线程只会有1个成功)

不过,针对更新成功率低的问题,在优惠券库存这个业务中,有一个乐观锁的改进方案:

我们无需判断issue_num是否与原来一致,只要判断issue_num是否小于total_num即可。这样,只要issue_num小于total_num,不管有多少线程来执行,都会成功。

综上,我们最终的执行SQL是这样的:

UPDATE coupon SET issue_num = issue_num + 1 WHERE id = 1 AND issue_num < total_num
AI 代码解读
舒莫
+关注
目录
打赏
0
0
0
0
2
分享
相关文章
面试官:如何保证本地缓存的一致性?
面试官:如何保证本地缓存的一致性?
1979 1
一种新的方法来存储用户信息——ThreadLocal
一种新的方法来存储用户信息——ThreadLocal
1331 0
还分不清 Cookie、Session、Token、JWT?看这一篇就够了
Cookie、Session、Token 和 JWT(JSON Web Token)都是用于在网络应用中进行身份验证和状态管理的机制。虽然它们有一些相似之处,但在实际应用中有着不同的作用和特点,接下来就让我们一起看看吧,本文转载至http://juejin.im/post/5e055d9ef265da33997a42cc
45452 13
MySQL单表数据不要超过500万行:是经验数值,还是黄金铁律?
原文地址:梁桂钊的博客 博客地址:http://blog.720ui.com 欢迎关注公众号:「服务端思维」。一群同频者,一起成长,一起精进,打破认知的局限性。 今天,探讨一个有趣的话题:MySQL 单表数据达到多少时才需要考虑分库分表?有人说 2000 万行,也有人说 500 万行。
20635 0
招行面试:亿级秒杀,超卖问题+少卖问题,如何解决?(图解+秒懂+史上最全)
45岁资深架构师尼恩在读者交流群中分享了如何系统化解决高并发下的库存抢购超卖少买问题,特别是针对一线互联网企业的面试题。文章详细解析了秒杀系统的四个阶段(扣库预扣、库存扣减、支付回调、库存补偿),并通过Redis分布式锁和Java代码示例展示了如何防止超卖。此外,还介绍了使用RocketMQ延迟消息和xxl-job定时任务解决少卖问题的方法。尼恩强调,掌握这些技术不仅能提升面试表现,还能增强实际项目中的高并发处理能力。相关答案已收入《尼恩Java面试宝典PDF》V175版本,供后续参考。
Redis系列学习文章分享---第五篇(Redis实战篇--优惠券秒杀,全局唯一id 添加优惠券 实现秒杀下单 库存超卖问题分析 乐观锁解决超卖 实现一人一单功能 集群下的线程并发安全问题)
Redis系列学习文章分享---第五篇(Redis实战篇--优惠券秒杀,全局唯一id 添加优惠券 实现秒杀下单 库存超卖问题分析 乐观锁解决超卖 实现一人一单功能 集群下的线程并发安全问题)
265 0
Spring AOP 详细深入讲解+代码示例
Spring AOP(Aspect-Oriented Programming)是Spring框架提供的一种面向切面编程的技术。它通过将横切关注点(例如日志记录、事务管理、安全性检查等)从主业务逻辑代码中分离出来,以模块化的方式实现对这些关注点的管理和重用。 在Spring AOP中,切面(Aspect)是一个模块化的关注点,它可以跨越多个对象,例如日志记录、事务管理等。切面通过定义切点(Pointcut)和增强(Advice)来介入目标对象的方法执行过程。 切点是一个表达式,用于匹配目标对象的一组方法,在这些方法执行时切面会被触发。增强则定义了切面在目标对象方法执行前、执行后或抛出异常时所
14752 3
【并发】高并发下库存超卖问题如何解决?
【并发】高并发下库存超卖问题如何解决?
4734 0
面试突击78:@Autowired 和 @Resource 有什么区别?
面试突击78:@Autowired 和 @Resource 有什么区别?
13421 5
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问