美文网首页
高并发下超卖问题及如何解决

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

作者: 尘世的鱼 | 来源:发表于2020-02-14 09:48 被阅读0次

    一般电子商务网站会有团购,秒杀等活动,而这样的活动特点是请求量激增,数以万计的用户会抢购一个商品,这样会面临活动商品库存有限,高并发下如何控制库存不出现超卖的问题。

    注意:

    1. 我们的数据存放在MySQL中
    2. 使用的语言是Java

    为何会发生超卖

    一般库存扣除的逻辑代码如下:

    //remainder为剩余库存数量
    int remainder=statement.query("select remainder from stock where stock_id='$STOCK_ID$'");
    
    //amount为本次订单抢购数量
    if(remainder>=amount!=0){
       statement.execute("update stock set remainder=remainder-amount where stock_id='$STOCK_ID$'")
    }
    

    防止超卖我们首先想到的是通过事务去解决这个问题:

    startTransaction();//开启事务
    
    try{
       //remainder为剩余库存数量
       int remainder=statement.query("select remainder from stock where   stock_id='$STOCK_ID$'");
    
       //amount为本次订单抢购数量
       if(remainder>=amount!=0){
           statement.execute("update stock set remainder=remainder-amount where stock_id='$STOCK_ID$'")
       } 
     }catch(Exception e){
        rollback();//回滚
    }
    
    commit();//提交事务
    

    上面的事务下执行逻辑其实隐藏一个很大的漏洞-有可能库存会变为负数(即超卖),具体分析如下:

    1. 由于是高并发,假设有三个用户a,b,c同时抢购该物品,并进入到了这个事务中,这三个用户查到的库存数是一样的(MySQL rr级别下总是读取事务开始时的行数据)
    2. 然后进入到update,假设这三个用户同时进入update操作,这个时候由于 行级锁的排他性限制,MySQL会将update操作串行化
    3. 上面update执行完后,有可能会发生库存变为负数的情况(超卖)

    怎么解决超卖

    最简单的方式("锁方式")

    针对上述超卖的情况我们可以通过更改下执行sql代码来实现:

    startTransaction();//开启事务
    
    try{
           statement.execute("update stock set remainder=remainder-amount where stock_id='$STOCK_ID$' and $remainder>=$amount")
       } 
     }catch(Exception e){
        rollback();//回滚
    }
    
    commit();//提交事务
    

    上面的修改可以杜绝库存超卖的现象。注意以上在MySQL一致性非锁定读(rr隔离级别下)。

    针对以上,我们可以换个思路,在库存数据结构中加入version字段来控制记录修改版本,也可以解决上述问题,如下:

    startTransaction();//开启事务
    
    try{
       //remainder为剩余库存数量
       int remainder=statement.query("select remainder,version from stock where   stock_id='$STOCK_ID$'");
    
       //amount为本次订单抢购数量
       if(remainder>=amount!=0){
           statement.execute("update stock set remainder=remainder-amount,version=version+1 where stock_id='$STOCK_ID$' and version=$version")
       } 
     }catch(Exception e){
        rollback();//回滚
    }
    
    commit();//提交事务
    

    但是我们真的能这么做吗?我们的业务可是高并发,面对的是1万+TPS,那如果按上面继续执行会遇到什么问题?

    很显然不能,在高并发下,会有很多这样的修改(update),每个请求都需要等待"锁",某些请求可能永远都获取不到锁,这种请求就会卡在那里,直到超时。同时,由于这种写请求很多,会造成大量的请求超时,连锁反应就是应用系统连接数被耗光,直至系统异常crash。即使重启系统,由于请求量大,系统也会立马挂掉。

    高并发下如何解决超卖

    引入缓存

    主要思路是:

    1. 首先在团购秒杀开始前将需要的物品库存信息放入缓存中
    2. 使用锁来处理其并发请求
    3. 将缓存中的数据同步到数据库。

    我们此处使用redis作为缓存。

    应用操作redis减库存的大体思路为:

    1. 首先通过redis api监听相关物品的库存信息,在事务开启前保证该物品库存信息无人修改
    2. 获取现有库存信息,判断库存不为0并且当前库存量大于等于订单所需数量
    3. 满足上述2的话则进行扣除操作
    4. 如果在1的过程中有别人更新了该物品库存信息版本,则重试
    5. 知道库存为0或者剩余库存不满足当前订单扣除数量退出

    具体代码如下:

     public void secondBuyProduct(Jedis jedis, String stockId, int orders) {
            //CAS重试
            while (true) {
                try {
                    //监视key,如果在后续事务执行之前key的值被其他命令所改动,那么事务将被打断
                    jedis.watch(stockId);
    
                    int prdNum = Integer.parseInt(jedis.get(stockId));
                    //判断库存是否满足订单数量要求
                    if (prdNum > 0 && prdNum - orders >= 0)) {
    
                        Transaction transaction = jedis.multi();
                        //减库存并写入
                        transaction.set(stockId, String.valueOf(prdNum - orders));
    
                        List<Object> res = transaction.exec();
                        //事务提交后如果为null,说明key值在本次事务提交前已经被改变,本次事务不执行。
                        if (res != null && !res.isEmpty()) {
                            System.out.println("抢购成功!");
                            break;
                        }
    
                    } else {
                        System.err.println("被抢光了!");
                        break;
                    }
                } catch (Exception e) {
                    System.err.println("抢购出错:" + e.toString());
                    e.printStackTrace();
                } finally {
                    jedis.unwatch();
                }
            }
        }
    

    相关文章

      网友评论

          本文标题:高并发下超卖问题及如何解决

          本文链接:https://www.haomeiwen.com/subject/dcqdfhtx.html