秒杀的几种方案(包括限制用户秒杀次数基于redis实现)

1.

 效率最低(同步关键字,低并发可以,高并发不行,10000个线程,甚至十几秒钟才扣减一个库存)

 for update 也是悲观锁,会锁住当前行,影响性能

1.乐观锁机制(加version版本号)

3.用mybatis自带的行锁,同一时间只能有一个线程修改某一行数据

直接上代码:

 

mapper代码:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.mapper.ProductDao">
    <!--FOR UPDATE 悲观锁没用-->
    <select id="getProductAmountById" resultType="com.example.entity.Product">
          SELECT id ,amount,version  FROM product WHERE id=1 FOR UPDATE;
    </select>

    <update id="subtracStock"  >
        UPDATE product set
          amount = amount - #{buyAmount},
          version = version+1
        WHERE id=1
    </update>

    <update id="subtracStockWithOptimistic"  >
        UPDATE product set
          amount = amount - #{buyAmount},
          version = version+1
        WHERE id=1 and version = #{version}
    </update>



    <!--不用乐观锁,用数据库提供的行锁来实现(强推)-->
    <update id="subtracStockNoOptimistic"  >
        UPDATE product set
          amount = amount - #{buyAmount}
        WHERE id=1 and amount >= #{buyAmount}+0
    </update>

dao:

 

import com.example.entity.Product;
import org.apache.ibatis.annotations.Param;

public interface ProductDao {

    int subtracStock(@Param("buyAmount") int buyAmoun);

    int subtracStockWithOptimistic(@Param("buyAmount") int buyAmount,@Param("version")  int version);

    Product getProductAmountById();


    int subtracStockNoOptimistic(@Param("buyAmount") int buyAmoun);
}

 

service:

import org.apache.ibatis.annotations.Param;

public interface ProductService {

    int subtracStock(Integer id, Integer buyAmout);


    int subtracStockOptimistic(Integer id, Integer buyAmout);


    int subtracStockNoOptimistic(int buyAmoun);




}

 

service 实现

 

package com.example.service;

import com.example.CustomException.NoStockException;
import com.example.entity.Product;
import com.example.mapper.ProductDao;
import com.example.redis.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;


@Service
public class ProductServiceImp implements ProductService {

    @Autowired
    public ProductDao productDao;

    @Autowired
    private RedisUtils redisUtils;


    //性能太差,并发不高的话可以使用(否则贼慢  10000个线程就不行了,库存减不动了)
    @Override
    public int   subtracStock(Integer productId,Integer buyAmount) {

        synchronized (ProductServiceImp.class){
            //判断库存
            Product product =   productDao.getProductAmountById();
            Integer amount =  product.getAmount();
            if(amount<=0){
                throw new NoStockException("no stock",406,"no stock");
            }
            if(amount>=buyAmount){
                productDao.subtracStock(buyAmount);
                System.out.println("--------");
            }else{
                throw new NoStockException("no stock",406,"less stock");
            }

        }


        return 0;
    }

    @Override
    public int subtracStockOptimistic(Integer id, Integer buyAmount) {
        //判断库存
        Product product =   productDao.getProductAmountById();
        if(product.getAmount()<=0){
            throw new NoStockException("no stock",406,"no stock");
        }
        if(product.getAmount()>=buyAmount){
            //错误:获取最新版本号,一样则进行修改(这个习惯错误)
            //正确:将 商品id,购物数量,自己的version 交给数据库执行,判断交给数据库(高效,不容易犯错)
            int result =   productDao.subtracStockWithOptimistic(buyAmount,product.getVersion());
            if(result == 1){
                return 1;
            }else{
                //进入重试阶段(为的是刚好有200的并发,200的库存,将库存都卖完,而不是200 线程进来,因为版本号不一致,放弃了扣减库存的机会)
                subtracStockOptimistic( id,  buyAmount);
            }

        }else{
            throw new NoStockException("no stock",406,"less stock");
        }

        return 0;
    }

    @Override
    public int subtracStockNoOptimistic(int buyAmoun) {
        //一分钟之内没有秒杀过的用户可以秒杀,否则不让秒杀
        Long start  = System.currentTimeMillis();
      Object isExist =   redisUtils.get("hasGetStock");
      if(null == isExist){
          //不需要重试也可以200个请求都把100 的库存减去
          int result  = productDao.subtracStockNoOptimistic(buyAmoun);
          //秒杀成功,将秒杀结果存到redis,防止同一个用户多次秒杀(薅羊毛)
          if(result ==0){
              redisUtils.set("hasGetStock",1,1L,TimeUnit.MINUTES);
              return 1;
          }
      }else{
          // redis 的ttl还可以做倒计时功能
          System.out.println("一分钟之内不可以多次秒杀");
          return 0;
       }

        return 0;
      }


}

 

controller:

//减库存
@GetMapping("/submitOrder")
public String submitOrder(Integer amount) {
    try {
        // 悲观锁并发低可以,并发量大的话,就严重影响性能(10000并发 扣库存很慢,甚至十几秒扣一个库存)
        //int a =  productService.subtracStock(1,amount);
        //乐观锁(推荐)
        //productService.subtracStockOptimistic(1,amount);
        //不用乐观锁(强推)
        productService.subtracStockNoOptimistic(amount);
        //问题:如何防止被恶意用户薅羊毛??(限制用户抢的次数)
        //成功后,由mq来生成订单(mq的好处:异步,可以提高用户响应时间)
    } catch(NoStockException nse) {
        throw new NoStockException("no stock",406,"no stock");
     } catch(Exception e) {
       throw new RuntimeException("Failed!!");
    }
    // 告知微信(支付宝)已经成功处理,不需要再发送回调
    return "success";
}

 

总结:

第三种方法性能最好,实现也简单

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值