• 抢购商品-实践一下


    前期准备:

      设计表结构:

        商品表

          

        购买记录表

          

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

      
     
  • 相关阅读:
    MyBatis框架(一)
    开始约定编程——Spring AOP
    Spring Boot快速入门
    全注解下的Spring IoC
    Java泛型
    Java异常
    windows监控web程序连接数
    winform导出excel报'object' does not contain a definition for 'get_Range'的问题
    git基本操作
    .net core中使用HttpClient碰到的问题:This instance has already started one or more requests. Properties can only be modified before sending the first request
  • 原文地址:https://www.cnblogs.com/helloworldmybokeyuan/p/11519560.html
Copyright © 2020-2023  润新知