超卖问题解决方案

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

我的逻辑是

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

这里是先查询、再判断、再更新的方案,而这三个步骤都是独立的,不具备原子性。在单线程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
目录
相关文章
|
2月前
|
消息中间件 缓存 负载均衡
秒杀系统优化:用解耦提升系统性能的秘诀!
大家好,我是小米,一个热爱分享技术经验的29岁程序员。本文主要探讨了解耦的概念及其在秒杀系统中的应用,特别是如何通过解耦提升系统的扩展性和容错能力。文中对比了HTTP/RPC同步调用和消息队列两种方式,分析了各自的优缺点及适用场景,帮助大家更好地选择合适的解耦方案。希望本文能让大家对解耦有更深入的理解。
85 8
秒杀系统优化:用解耦提升系统性能的秘诀!
|
5月前
|
NoSQL Redis
Redis系列学习文章分享---第五篇(Redis实战篇--优惠券秒杀,全局唯一id 添加优惠券 实现秒杀下单 库存超卖问题分析 乐观锁解决超卖 实现一人一单功能 集群下的线程并发安全问题)
Redis系列学习文章分享---第五篇(Redis实战篇--优惠券秒杀,全局唯一id 添加优惠券 实现秒杀下单 库存超卖问题分析 乐观锁解决超卖 实现一人一单功能 集群下的线程并发安全问题)
126 0
|
6月前
|
消息中间件 存储 NoSQL
面试题解析:如何解决分布式秒杀系统中的库存超卖问题?
面试题解析:如何解决分布式秒杀系统中的库存超卖问题?
374 0
|
NoSQL 关系型数据库 MySQL
聊聊高并发下超卖,少卖的解决方案
聊聊高并发下超卖,少卖的解决方案
391 0
|
SQL 缓存 监控
掌握了这些优化技巧,再也不用担心接口性能上不去了!
优化接口性能对每个后端开发同学来说见惯不惯了,也是一项必备的技能,因为我们平时开发中都会对外提供接口,性能差的话,功能多少会有影响。
|
SQL NoSQL 关系型数据库
【并发】高并发下库存超卖问题如何解决?
【并发】高并发下库存超卖问题如何解决?
2292 0
|
数据采集 存储 调度
使用多线程爬虫提高商品秒杀系统的吞吐量处理能力
使用多线程爬虫提高商品秒杀系统的吞吐量处理能力
|
消息中间件 安全 Java
实现高并发秒杀的 7 种方式,写的太好了,建议收藏!!
实现高并发秒杀的 7 种方式,写的太好了,建议收藏!!
实现高并发秒杀的 7 种方式,写的太好了,建议收藏!!
|
存储 开发框架 负载均衡
限流的非常规用途 - 缓解抢购压力
限流的非常规用途 - 缓解抢购压力
116 0
|
SQL Web App开发 BI
高并发-【抢红包案例】之二:使用悲观锁方式修复红包超发的bug
高并发-【抢红包案例】之二:使用悲观锁方式修复红包超发的bug
103 0