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

1.

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

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

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

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

直接上代码:

 

mapper代码:





    
    

    
        UPDATE product set
          amount = amount - #{buyAmount},
          version = version+1
        WHERE id=1
    

    
        UPDATE product set
          amount = amount - #{buyAmount},
          version = version+1
        WHERE id=1 and version = #{version}
    



    
    
        UPDATE product set
          amount = amount - #{buyAmount}
        WHERE id=1 and amount >= #{buyAmount}+0
    

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";
}

 

总结:

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

你可能感兴趣的:(秒杀)