zoukankan      html  css  js  c++  java
  • 抢购商品-实践一下

    前期准备:

      设计表结构:

        商品表

          

        购买记录表

          

      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秒,非常快。

      
     
  • 相关阅读:
    Java实现 蓝桥杯VIP 算法训练 传球游戏
    Java实现 蓝桥杯VIP 算法训练 Hanoi问题
    Java实现 蓝桥杯VIP 算法训练 蜜蜂飞舞
    Java实现 蓝桥杯VIP 算法训练 奇偶判断
    Java实现 蓝桥杯VIP 算法训练 传球游戏
    Java实现 蓝桥杯VIP 算法训练 Hanoi问题
    Java实现 蓝桥杯VIP 算法训练 Hanoi问题
    Java实现 蓝桥杯VIP 算法训练 蜜蜂飞舞
    Java实现 蓝桥杯VIP 算法训练 蜜蜂飞舞
    Qt: 访问容器(三种方法,加上for循环就四种了)good
  • 原文地址:https://www.cnblogs.com/helloworldmybokeyuan/p/11519560.html
Copyright © 2011-2022 走看看