高并发下如何解决超卖问题

限流:通过配置sentinel解决

队列、异步

                   高并发下如何解决超卖问题

 通过加锁sychronized或者lock来说定扣减优惠券这一步的化,出现的问题是:sychronized作用范围是单个jvm实例,对于集群分布就失效了,且单机jvm加锁之后变成串行效率下降

可以用分布式锁,但是分布式锁过于笨重性能下降。

 高并发下如何解决超卖问题

 

有时为了解决ABA问题,还需要加上一个版本号字段,然后判断版本号是否相同。

扣减库存的多种sql适用场景

第一种 :  update product set stock=stock-1 where id = 1 and stock>0

第二种: update product set stock=stock-1 where stock=#{原先查询的库存} and id = 1 and stock>0

第三种 :update product set stock=stock-1,versioin = version+1 where id = 1 and stock>0 and
version=#{原先查询的版本号}

核心是解决超卖问题,防止库存为负数:

对于方案1 :id是主键索引的前提下,如果每次只是减少一个库存,则可以才采用,只做数据安全的校验,可以有效减库存,性能更高,避免大量无效sql,只要有库存就可以操作成功

场景: 高并发场景下的取号器,优惠券发放扣减库存

方案2:使用业务自身的条件作为乐观锁,但是存在ABA问题,对比方案3 的好处是不用增加version版本号,如果只是扣减库存且不关心ABA问题,则可以采用。

方案3:增加版本号主要是为了解决ABA 问题,数据读取后,更新前数据被人篡改过,version只能做递增。

场景:商品秒杀,优惠券方法,需要记录库存操作前后的业务。

个人超领优惠券

领取优惠券的过程中,校验和保存到数据库两步没有加分布式锁,导致个人超领优惠券;

实现分布式锁的方法有mysql、redis、zookeeper

高并发下如何解决超卖问题

 分布式锁的框架redisson

 分布式锁高并发下如何解决超卖问题https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95

 使用分布式锁插件

1、导入一俩到common模块

      <!--分布式锁-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.10.1</version>

 2、配置分布式锁,在commoon中

@Configuration
@Data
public class APPConfig {

    @Value("${spring.redis.host}")
    private String redisHost;

    @Value("${spring.redis.port}")
    private String redisPort;

    @Value("${spring.redis.password}")
    private String redispwd;

    /**
     * 配置分布式锁的redisson
     * @return
     */
    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        // 单机方式
        config.useSingleServer().setPassword(redispwd).setAddress("redis://"+redisHost+":"+redisPort);
        // 集群模式
       // config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

3、使用分布式锁

 @GetMapping("lock")
    public JsonData testLock(){

        // 分布式锁与这个以及一样
//        ReentrantLock reentrantLock = new ReentrantLock();
//        reentrantLock.lock();
//        reentrantLock.unlock();

        RLock lock = redissonClient.getLock("lock:coupon:1");
//        // 阻塞等待时间
//        lock.lock(10, TimeUnit.SECONDS);

        lock.lock();

        //业务逻辑
        try{
            log.info("加锁成功,处理业务逻辑"+Thread.currentThread().getId());
            TimeUnit.SECONDS.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            log.info("解锁成功");
            lock.unlock();
        }

        return JsonData.buildSuccess();
    }

高并发下如何解决超卖问题

 redisson如何解决过期时间小于业务的执行时间:

采用看门狗的方式;

              高并发下如何解决超卖问题

 看门狗的检查锁的超时时间是30s。

     // 多个线程进入会阻塞,等待锁释放  有看门狗,默认30s
        rlock.lock();
        
     // 加锁10s钟过期,没有watch dog 功能
        rlock.lock(10,TimeUnit.SECONDS);

高并发下如何解决超卖问题

高并发下如何解决超卖问题 

 项目中设置事务的时候,先在启动类中开启事务管理,然后再需要事务的方法上面添加事务的注解。

 

上一篇:restController与Controller-待续


下一篇:算法题解之math类题