超卖等高并发秒杀场景的问题及解决方案

这篇具有很好参考价值的文章主要介绍了超卖等高并发秒杀场景的问题及解决方案。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 超卖问题(多人秒杀)

1.1 原因

  • 多线程并行运行
  • 多行代码操作共享资源,但不具备原子性

例:
超卖等高并发秒杀场景的问题及解决方案,各类优化方案,java,数据库,服务器

1.2 解决方案

针对并发安全问题,最广为人知的解决方案就是加锁
从实现思想上来说,锁可以分为两大类:

  • 悲观锁
  • 乐观锁

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

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

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

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

乐观锁:

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

1.3 总结

超卖这样的线程安全问题,解决方案有哪些?

  • 悲观锁:添加同步锁,让线程串行执行
    • 优点:简单粗暴
    • 缺点:性能一般
  • 乐观锁:不加锁,在更新时判断是否有其它线程在修改
    • 优点:性能好
    • 缺点:存在成功率低的问题

2. 锁失效问题(单人重复抢)

2.1 原因

使用Synchronized的代码块作为锁时,锁使用的是方法中的变量时,此时一般会通过tostring()方法将变量转为常量,但由于tostring的底层方法中使用的是:
超卖等高并发秒杀场景的问题及解决方案,各类优化方案,java,数据库,服务器
这种new出来的对象并不相同,从而导致锁不相同,从而引发锁失效的问题。

2.2 解决方案

String类中提供了intern()方法:
超卖等高并发秒杀场景的问题及解决方案,各类优化方案,java,数据库,服务器
从描述中可以看出,只要两个字符串equals的结果为true,那么intern就能保证得到的结果用 ==判断也是true,其原理就是获取字符串字面值对应到常量池中的字符串常量。因此只要两个字符串一样,intern()返回的一定是同一个对象。

3. 事务边界问题(单人重复抢)

3.1 原因

由于锁过早释放,导致了事务尚未提交,并发的线程判断出现错误,最终导致并发安全问题发生。
这其实就是事务边界和锁边界的问题。
超卖等高并发秒杀场景的问题及解决方案,各类优化方案,java,数据库,服务器

3.2 解决方案

解决方案很简单,就是调整边界:

  • 业务开始前,先获取锁,再开启事务
  • 业务结束后:先提交事务,再释放锁

将加锁的部分抽取为一个方法,在方法上加@Transactional事务注解,在调用的方法中将抽取的方法放在锁内即可解决
超卖等高并发秒杀场景的问题及解决方案,各类优化方案,java,数据库,服务器

3.3 总结

在事务和锁并行存在时,一定要考虑事务和锁的边界问题。由于事务的隔离级别问题,可能会导致不同事务之间数据不可见,往往会产生一些不可预期的现象。

4. 事务失效问题

4.1 原因

虽然事务边界问题已经解决,但是其解决方案也埋下了新的隐患。

常见的事务失效原因:

4.1.1 事务方法非public修饰

由于Spring的事务是基于AOP的方式结合动态代理来实现的。因此事务方法一定要是public的,这样才能便于被Spring做事务的代理和增强。
而且,在Spring内部也会有一个 org.springframework.transaction.interceptor.AbstractFallbackTransactionAttributeSource类,去检查事务方法的修饰符:

@Nullable
 protected TransactionAttribute computeTransactionAttribute(
  Method method, @Nullable Class<?> targetClass) {
   // Don't allow non-public methods, as configured.
   if (allowPublicMethodsOnly() && 
  !Modifier.isPublic(method.getModifiers())) {
      return null;
   }

    // ... 略

   return null;
 }

所以,事务方法一定要被public修饰!

4.1.2 非事务方法调用事务方法

@Service
public class OrderService {
    
    public void createOrder(){
        // ... 准备订单数据
        
        // 生成订单并扣减库存
        insertOrderAndReduceStock();
    }
    
    @Transactional
    public void insertOrderAndReduceStock(){
        // 生成订单
        insertOrder();
        // 扣减库存
        reduceStock();
    }
}

可以看到,insertOrderAndReduceStock方法是一个事务方法,肯定会被Spring事务管理。Spring会给OrderService类生成一个动态代理对象,对insertOrderAndReduceStock方法做增加,实现事务效果。

但是现在createOrder方法是一个非事务方法,在其中调用了insertOrderAndReduceStock方法,这个调用其实隐含了一个this.的前缀。也就是说,这里相当于是直接调用原始的OrderService中的普通方法,而非被Spring代理对象的代理方法。那事务肯定就失效了!

4.1.3 事务方法的异常被捕获了

示例:

 @Service
 public class OrderService {

    @Transactional
    public void createOrder(){
        // ... 准备订单数据
        // 生成订单
        insertOrder();
        // 扣减库存
        reduceStock();
    }

    private void reduceStock() {
        try {
            // ...扣库存
        } catch (Exception e) {
            // 处理异常
        }
    }

 }

在这段代码中,reduceStock方法内部直接捕获了Exception类型的异常,也就是说方法执行过程中即便出现了异常也不会向外抛出。
而Spring的事务管理就是要感知业务方法的异常,当捕获到异常后才会回滚事务。
现在事务被捕获,就会导致Spring无法感知事务异常,自然不会回滚,事务就失效了。

4.1.4 事务异常类型不对

 
@Service
 public class OrderService {

    @Transactional(rollbackFor = RuntimeException.class)
    public void createOrder() throws IOException {
        // ... 准备订单数据
        
        // 生成订单
        insertOrder();
        // 扣减库存
        reduceStock();

        throw new IOException();
    }
 }

Spring的事务管理默认感知的异常类型是RuntimeException,当事务方法内部抛出了一个IOException时,不会被Spring捕获,因此就不会触发事务回滚,事务就失效了。

因此,当我们的业务中会抛出RuntimeException以外的异常时,应该通过@Transactional注解中的rollbackFor属性来指定异常类型:

@Transactional(rollbackFor = Exception.class)

4.1.5 事务传播行为不对

 
@Service
 public class OrderService {
    @Transactional
    public void createOrder(){
        // 生成订单
        insertOrder();
        // 扣减库存
        reduceStock();
        throw new RuntimeException("业务异常");
    }
    @Transactional
    public void insertOrder() {
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void reduceStock() {
    }
 }

在示例代码中,事务的入口是createOrder()方法,会开启一个事务,可以成为外部事务。在createOrder()方法内部又调用了insertOrder()方法和reduceStock()方法。这两个都是事务方法。
不过,reduceStock()方法的事务传播行为是REQUIRES_NEW,这会导致在进入reduceStock()方法时会创建一个新的事务,可以成为子事务。insertOrder()则是默认,因此会与createOrder()合并事务。
因此,当createOrder方法最后抛出异常时,只会导致insertOrder方法回滚,而不会导致reduceStock方法回滚,因为reduceStock是一个独立事务。
所以,一定要慎用传播行为,注意外部事务与内部事务之间的关系。

4.1.6 没有被Spring管理

 
//  @Service
 public class OrderService {
    @Transactional
    public void createOrder(){
        // 生成订单
        insertOrder();
        // 扣减库存
        reduceStock();
        throw new RuntimeException("业务异常");
    }
    @Transactional
    public void insertOrder() {
    }
    @Transactional
    public void reduceStock() {
    }
 }

这个示例属于比较低级的错误,OrderService类没有添加@Service注解,因此就没有被Spring管理。你在方法上添加的@Transactional注解根本不会有人帮你动态代理,事务自然失效。文章来源地址https://www.toymoban.com/news/detail-613585.html

到了这里,关于超卖等高并发秒杀场景的问题及解决方案的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Redis】电商项目秒杀问题之超卖问题与一人一单问题

    目录 一、超卖问题 1、背景 2、产生原因以及线程安全问题 3、解决 1.悲观锁 2.乐观锁 4、新的问题 5、解决 二、一人一单 1、背景 2、产生原因以及线程安全问题 3、解决 4、新的问题 5、解决 三、集群下的并发问题 1、说明 2、解决 在如双11等购物需求剧增的背景下,一个物品

    2024年02月04日
    浏览(54)
  • 互联网中的商品超卖问题及其解决方案:Java中Redis结合UUID的应用

    在设计商品下单和库存扣减,你一定遇到过这样的问题,库存扣减为0了,可是消费者还能下单,并将订单信息保存到了数据库里,针对商品超卖问题,作此篇以解决。 随着互联网商业的飞速发展,商品超卖问题逐渐凸显为电商平台面临的一大挑战。尤其是在大型促销活动期

    2024年02月04日
    浏览(31)
  • 并发安全问题之超卖问题

    并发安全问题之超卖问题 乐观锁总结: 优点 :不加锁性能好。 缺点 :同时请求成功率低(即只要发现数据变了就放弃了)。 乐观锁思想的具体体现 :一共两步,第一步,先查询状态。第二步,再更新数据时必须where等于前面的状态,确保数据没有改变。 第二步集查询和操

    2024年02月08日
    浏览(33)
  • JMeter压力测试案例(商品超卖并发问题)

    压力测试可以用来验证软件系统的稳定性和可靠性,在压力下测试系统的性能和稳定性,发现并解决潜在的问题,确保系统在高负载情况下不会崩溃。 压力测试可以用来评估软件系统的容量和性能,通过模拟高负载情况下的用户访问量和数据量,测试系统的处理能力和响应速

    2024年02月05日
    浏览(27)
  • 论文-分布式-并发控制-并发控制问题的解决方案

    目录 参考文献 问题 解法与证明 易读版本 参考文献 Dijkstra于1965年发表文章Solution of a Problem in Concurrent Programming Control,引出并发系统下的互斥(mutual exclusion)问题,自此开辟了分布式计算领域 Dijkstra在文中给出了基于共享存储原子性访问的解决方案只有十多行代码,但阅读起来

    2024年02月08日
    浏览(36)
  • 深入探讨MySQL并发事务的问题及解决方案

    人不走空                                                                          目录         🌈个人主页:人不走空       💖系列专栏:算法专题 ⏰诗词歌赋:斯是陋室,惟吾德馨 1. 脏读(Dirty Read) 2. 不可重复读(Non-repeatable Read) 3. 幻读(Phantom Rea

    2024年03月09日
    浏览(80)
  • 高并发场景下,6种解决SimpleDateFormat类的线程安全问题方法

    摘要: 解决SimpleDateFormat类在高并发场景下的线程安全问题可以有多种方式,这里,就列举几个常用的方式供参考。 本文分享自华为云社区《【高并发】更正SimpleDateFormat类线程不安全问题分析的错误》,作者: 冰 河 。 解决SimpleDateFormat类在高并发场景下的线程安全问题可以

    2024年02月11日
    浏览(34)
  • redis的缓存穿透,缓存并发,缓存雪崩,缓存问题及解决方案

    缓存穿透 问题原因 解决方案 缓存并发 缓存雪崩 缓存失效时间设置一致导致的。 解决方案: 1)方案一 2)方案二 如何设计一个缓存策略,缓存热点数据?

    2024年02月21日
    浏览(29)
  • 秒杀库存解决方案

    电商系统中秒杀是一种常见的业务场景需求,其中核心设计之一就是如何扣减库存。本篇主要分享一些常见库存扣减技术方案,库存扣减设计选择并非一味追求性能更佳,更多的应该考虑根据实际情况来进行架构取舍。在商品购买的过程中,库存的抵扣过程通常包括以下步骤

    2024年02月13日
    浏览(22)
  • 【Redis】电商项目秒杀问题之下单接口优化:Redis缓存、MQ以及lua脚本优化高并发背景下的秒杀下单问题

    目录 一、优化思路 二、缓存库存与订单 1、库存缓存的redis数据结构 2、订单信息缓存的redis数据结构 三、整体流程 四、lua脚本确保权限校验操作的原子性 【Redis】电商项目秒杀问题之超卖问题与一人一单问题_1373i的博客-CSDN博客 https://blog.csdn.net/qq_61903414/article/details/1305689

    2024年02月05日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包