秒杀

148 阅读2分钟

image.png

@SpringBootTest(classes = xxx.class)
@ComponentScan(basePackages = "com.xx.xx.xx")
public class SecKill {
    @Resource
    private RedissonClient redissonClient;
    
    String delayQueue = "secKill-delay-queue";
    
    @Test
    public void kill() {
        ExecutorService executorService = new ThreadPoolExecutor(500, 1000, 500, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10), new ThreadPoolExecutor.CallerRunsPolicy());
        for (int i = 1; i < 20000; i++) {
            int userId = i;
            executorService.execute(() -> {
                tryKill(userId); 
             }); 
        }
        try {
            TimeUnit.SECONDS.sleep(100); 
        } catch (InterruptedException e) {
            e.printStackTrace(); 
        } 
    }
    
    
    //1.发起秒杀
    @Test
    public void tryKill(Integer userId) {
        //1.1验证码是否正确
        //TODO
        //1.2是否限流,判断消息队列长度。 例如,在秒杀活动中,我们出售1000件商品,此时在消息队列中存在1000个请求,如果后续仍然有用户发起秒杀请求,则后续的请
        //求我们可以不再处理,直接向用户返回商品已售完的提示。 
        RBlockingQueue<String> killBlockingQueue = redissonClient.getBlockingQueue("secKill-queue");
        int queueSize = killBlockingQueue.size();
        AssertUtils.isTrue(queueSize < 600, "商品已售罄" + userId);
        //1.3发送mq消息
        killBlockingQueue.offer(userId.toString());
    }
    
    
    //2.MQ异步执行消息内容
    @Test
    public void genToken() {
        RBlockingQueue<String> secKillQueue = redissonClient.getBlockingQueue("secKill-queue"); 
        try {
            while (true) {
                String userId = secKillQueue.take(); 
                //2.1活动是否结束 todo 
                
                //2.2是否黑名单(拦截器统计访问次数) todo 
                
                //2.3扣减缓存中的商品秒杀库存 
                //RLongAdder atomicLong = redisson.getLongAdder("myLongAdder");
                //atomicLong.increment();
                
                RAtomicLong rAtomicLong = redissonClient.getAtomicLong("kill-stock");
                boolean exists = rAtomicLong.isExists();
                //AssertUtils.isTrue(killStockCount != null,"活动已经结束");
                if (!exists) {
                    System.out.println("活动已经结束");
                    continue; 
                } 
                
                if (rAtomicLong.get() < 1) {
                    System.out.println("库存不足");
                    continue; 
                } 
                long count = rAtomicLong.getAndDecrement();
                //AssertUtils.isTrue(count>0,"库存不足");
                if (count < 1) {
                    System.out.println("库存不足");
                    continue; 
                } 
                //2.4生成token 
                //生成token并且存入redis内并给一个5分钟的有效期 
                String token = UUID.randomUUID().toString().replace("-", "");
                //秒杀促销id 
                String promoId = "1";
                String promo_token = "promo_token:promo_token_" + promoId + "_user_id_" + userId;
                redissonClient.getBucket(promo_token).set(token, 2, TimeUnit.MINUTES);
                //延迟队列,用于恢复扣减掉的库存
                RBlockingQueue<String> killBlockingQueue = redissonClient.getBlockingQueue(delayQueue);
                RDelayedQueue<String> killDelayedQueue = redissonClient.getDelayedQueue(killBlockingQueue);
                killDelayedQueue.offer(promo_token, new Random().nextInt(100), TimeUnit.SECONDS);
                System.out.println(String.format(" %s userId = %s , token= %s", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), userId, token)); 
            }
        } catch (InterruptedException e) {
            e.printStackTrace(); 
        } 
    } 
    
    
    //3.查询秒杀资格(第一步1.发起秒杀返回成功后,前端开始轮训3秒,检查是否获得令牌)
    @Test 
    public void checkKillQualification() {
        String promoId = "1";
        String userId = ""; 
        String promo_token = "promo_token_" + promoId + "user_id_" + userId;
        RBucket<String> rBucket = redissonClient.getBucket(promo_token);
        String token = rBucket.get(); 
        AssertUtils.isTrue(StringUtils.hasText(token), "抢购商品失败");
        //抢货成功
        // long count = redissonClient.getAtomicLong("kill-stock").getAndDecrement();
        // AssertUtils.isTrue(count>0,"库存不足"); 
    } 
    
    //4.秒杀结算
    @Test 
    public void secSettlement(Integer userId) {
        //4.1验证下单token
        //前端传进来的
        token String userToken = "kk";
        String promoId = "1";
        String promo_token = "promo_token:promo_token_" + promoId + "user_id_" + userId;
        RBucket<String> rBucket = redissonClient.getBucket(promo_token);
        String serviceToken = rBucket.get();
        AssertUtils.isTrue(userToken.equals(serviceToken), "秒杀令牌已过期");

        //4.2加入秒杀购物车表
        cart_item Map<String, String> cartItem = new HashMap<>(); cartItem.put("promo_id", "1");
        cartItem.put("user_id_", "1001");
        cartItem.put("amount", "0.01");
        cartItem.put("pay_status", "1"); 
    } 
        
    //5.提交订单
    @Test
    public void submitOrder(Integer userId) {
        //从购物车中获取秒杀的活动
        Map<String, String> cartItem = new HashMap<>();
        Map<String, String> order = new HashMap<>();
        order.put("promo_id", cartItem.get("promo_id"));
        order.put("user_id_", cartItem.get("user_id_"));
        order.put("total_amount", cartItem.get("amount"));
        order.put("pay_amount", "0.01");
        //insert order 

        //删除秒杀
        Token String promoId = "1";
        String promo_token = "promo_token_" + promoId + "user_id_" + userId; 
        redissonClient.getBucket(promo_token).delete();
     } 

     //6.异步回滚商品库存
     @Test
     public void rollbackStock() {
         RBlockingQueue<String> rBlockingQueue = redissonClient.getBlockingQueue(delayQueue);
         //必须加上获取延迟队列,否则应用重启后,无法消费到超时消息 
         redissonClient.getDelayedQueue(rBlockingQueue);
         while (true){
             try {
                 String token = rBlockingQueue.take();
                 //幂等校验 已支付的不处理 
                 // boolean alreadyPay = true;
                 // if(alreadyPay){ 
                     // continue; 
                 // } 
                 long count = redissonClient.getAtomicLong("kill-stock").incrementAndGet(); 
                 System.out.println(String.format("恢复库存 = %s",count)); 
              } catch (InterruptedException e) {
                  e.printStackTrace(); 
              } 
        } 
    } 
}