超卖问题解决方案

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

我的逻辑是

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

这里是先查询、再判断、再更新的方案,而这三个步骤都是独立的,不具备原子性。在单线程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
目录
相关文章
|
3月前
|
NoSQL Redis
Redis系列学习文章分享---第五篇(Redis实战篇--优惠券秒杀,全局唯一id 添加优惠券 实现秒杀下单 库存超卖问题分析 乐观锁解决超卖 实现一人一单功能 集群下的线程并发安全问题)
Redis系列学习文章分享---第五篇(Redis实战篇--优惠券秒杀,全局唯一id 添加优惠券 实现秒杀下单 库存超卖问题分析 乐观锁解决超卖 实现一人一单功能 集群下的线程并发安全问题)
84 0
|
4月前
|
消息中间件 存储 NoSQL
面试题解析:如何解决分布式秒杀系统中的库存超卖问题?
面试题解析:如何解决分布式秒杀系统中的库存超卖问题?
251 0
|
11月前
|
NoSQL 关系型数据库 MySQL
聊聊高并发下超卖,少卖的解决方案
聊聊高并发下超卖,少卖的解决方案
302 0
|
SQL NoSQL 关系型数据库
【并发】高并发下库存超卖问题如何解决?
【并发】高并发下库存超卖问题如何解决?
1648 0
|
设计模式 架构师 算法
这个时代,达不到百万以上并发量都不叫高并发!!收藏学以致用
成为一名年薪百万的顶尖架构师,实现财富自由,是大多数JAVA高级程序员的职业追求。 这不仅是技术发展的趋势,同时也是个人职业价值的体现。 但最终能否成为IT架构中的「灵魂人物」,做出亿级用户量的产品、搭建承载百万级并发的架构,还要取决于你能不能翻过并发量这道坎。
|
数据采集 存储 调度
使用多线程爬虫提高商品秒杀系统的吞吐量处理能力
使用多线程爬虫提高商品秒杀系统的吞吐量处理能力
|
消息中间件 安全 Java
实现高并发秒杀的 7 种方式,写的太好了,建议收藏!!
实现高并发秒杀的 7 种方式,写的太好了,建议收藏!!
实现高并发秒杀的 7 种方式,写的太好了,建议收藏!!
|
存储 开发框架 负载均衡
限流的非常规用途 - 缓解抢购压力
限流的非常规用途 - 缓解抢购压力
93 0
|
SQL Web App开发 BI
高并发-【抢红包案例】之二:使用悲观锁方式修复红包超发的bug
高并发-【抢红包案例】之二:使用悲观锁方式修复红包超发的bug
85 0
|
NoSQL Java API
分布式锁主动续期的入门级实现-自省 | 简约而不简单
分布式锁主动续期的入门级实现-自省 | 简约而不简单
126 0
分布式锁主动续期的入门级实现-自省 | 简约而不简单