抢购商品-实践一下

前期准备:

  设计表结构:

    商品表

      

    购买记录表

      

  dao设计

    

    <!--获取产品-->
    <select id="getProduct" parameterType="long" resultType="product">
          select id,product_name as productName,
          stock,price,version,note from t_product
          where id=#{id}
    </select>
    <!--减少库存-->
    <update id="decreaseProduct">
            update t_product set stock = stock - #{quantity}
            where id = #{id}
    </update>

  

    <!--插入购买记录-->
    <insert id="insertPurchaseRecord" parameterType="purchaseRecord">
            insert into t_purchase_record(
            user_id,product_id,price,quantity,sum,purchase_date,note)
            values(#{userId},#{productId},#{price},#{quantity},
            #{sum},now(),#{note})
    </insert>

  service层设计

 @Transactional
    public boolean purchase(Long userId, Long productId, int quantity) {
        // 获取产品
        ProductPo product = productMapper.getProduct(productId);
        // 比较库存与购买量
        if (product.getStock()<quantity){
            return false;
        }
        // 扣减库存
        productMapper.decreaseProduct(productId,quantity);
        // 创建购买记录
        PurchaseRecordPo pr = this.initPurchaseRecord(userId,product,quantity);
        // 插入购买记录
        purchaseRecordMapper.insertPurchaseRecord(pr);
        return true;
    }

    private PurchaseRecordPo initPurchaseRecord(Long userId, ProductPo product, int quantity) {
        PurchaseRecordPo pr = new PurchaseRecordPo();
        pr.setNote("购买日志,时间:" + System.currentTimeMillis());
        pr.setProductId(product.getId());
        pr.setPrice(product.getPrice());
        pr.setQuantity(quantity);
        double sum = product.getPrice() * quantity;
        pr.setSum(sum);
        pr.setUserId(userId);
        return pr;
    }

  controller层设计

 @GetMapping("/purchase")
    public String purchase(){
        return "purchase";
    }
    @PostMapping("/purchase")
    @ResponseBody
    public Result purchase(Long userId,Long productId,Integer quantity){
        boolean success = purchaseService.purchase(userId,productId,quantity);
        String message = success? "抢购成功":"抢购失败";
        Result result = new Result(success,message);
        return result;
    }

    class Result{
        private boolean success;
        private String message;

        public Result() {
        }

        public Result(boolean success, String message) {
            this.success = success;
            this.message = message;
        }

        public boolean isSuccess() {
            return success;
        }

        public void setSuccess(boolean success) {
            this.success = success;
        }

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }
    }

  前端页面调用

     <script type="text/javascript" src="/js/jquery-1.8.3.js"></script>
    <script type="text/javascript">
        for(var i=0;i<50000;i++){
            var params = {
                userId:1,
                productId:1,
                quantity:1
            };
            $.post("/purchase/purchase",params,function (result) {
                //alert(result.message);
            })
        }
    </script>

  五万人抢两万件商品结果如下

  可看出出现超发现象,卖出去了20003件商品,库存变为-3

  那么这样的问题应该如何来解决呢?当前企业中提出了悲观锁、乐观锁、redis等解决方案

  一、悲观锁

    <!--获取产品-->
    <select id="getProduct" parameterType="long" resultType="product">
          select id,product_name as productName,
          stock,price,version,note from t_product
          where id=#{id} for update
    </select>
  数据库事务执行的过程中 就会锁定查询出来的数据 其他的事务将不能再对其进行读写,这样就避免了数据的不 单个请求直至数据 事务完成,才会释放这个锁,其他的请求才能重新得 这个锁 
 
  结果正确,但第一次我耗时们耗时51秒,这次我们一共花费了一分零5秒,花费了更多的时间,那如何减少需要花费的时间呢?
 
  二、乐观锁
    <!--减少库存-->
    <update id="decreaseProduct">
        update t_product set stock = stock - #{quantity},
        version = version + 1
        where id = #{id} and version = #{version}
    </update>

  别忘记去掉for update,并同步修改dao减少库存接口加上第三个参数

 @Transactional
    public boolean purchase(Long userId, Long productId, int quantity) {
        // 获取产品
        ProductPo product = productMapper.getProduct(productId);
        // 比较库存与购买量
        if (product.getStock()<quantity){
            return false;
        }
        
        // 获取当前版本号
        int version = product.getVersion();
        // 尝试扣减库存
        int result = productMapper.decreaseProduct(productId, quantity, version);
        if (result == 0) {
            return false;
        }

        // 创建购买记录
        PurchaseRecordPo pr = this.initPurchaseRecord(userId,product,quantity);
        // 插入购买记录
        purchaseRecordMapper.insertPurchaseRecord(pr);
        return true;
    }

  结果如下:卖出5447,剩余14553件产品

    这次时间是快了,但是却剩余了大量的产品,为什么呢,因为并发操作时好多操作都被判定失败了。

  改进方法:

    使用时间戳限制重入的乐观锁
    
public boolean purchase(Long userId, Long productId, int quantity) {
        // 当前时间
        long start = System.currentTimeMillis();
        // 循环尝试直到成功
        while(true){
            // 循环时间
            long end = System.currentTimeMillis();
            if (end - start>100){
                return false;
            }

            // 获取产品
            ProductPo product = productMapper.getProduct(productId);
            // 比较库存与购买量
            if (product.getStock()<quantity){
                return false;
            }

            // 获取当前版本号
            int version = product.getVersion();
            // 尝试扣减库存
            int result = productMapper.decreaseProduct(productId, quantity, version);
            if (result == 0) {
                continue;
            }

            // 创建购买记录
            PurchaseRecordPo pr = this.initPurchaseRecord(userId,product,quantity);
            // 插入购买记录
            purchaseRecordMapper.insertPurchaseRecord(pr);
            return true;
        }

    }
  结果如下:  吐血,可能由于本人电脑问题,剩余商品反而更多了,剩余18698,卖出1302,耗时1分27秒
  使用限定次数重入的乐观锁
    @Transactional
    public boolean purchase(Long userId, Long productId, int quantity) {
        for(int i=0;i<3;i++){
            // 获取产品
            ProductPo product = productMapper.getProduct(productId);
            // 比较库存与购买量
            if (product.getStock()<quantity){
                return false;
            }

            // 获取当前版本号
            int version = product.getVersion();
            // 尝试扣减库存
            int result = productMapper.decreaseProduct(productId, quantity, version);
            if (result == 0) {
                continue;
            }

            // 创建购买记录
            PurchaseRecordPo pr = this.initPurchaseRecord(userId,product,quantity);
            // 插入购买记录
            purchaseRecordMapper.insertPurchaseRecord(pr);
            return true;
        }
        return false;
    }

  结果如下:这次还好,剩余6550,卖出13450,耗时1分16秒

三、那有没有更好的方法呢,有的那就是使用redis

   采用lua脚本在内存中操作数据进行抢购,再通过定时任务的方式将其写入到数据库中,总用时15秒,非常快。

  
 

猜你喜欢

转载自www.cnblogs.com/helloworldmybokeyuan/p/11519560.html