springboot 订单压测

发布时间 2023-11-04 00:49:43作者: Amani_Bey

首先查出商品的库存存入redis中预热:

例如:

 

控制器:

 @Autowired
    private SeckillQueueService queueService;

    @Autowired
    RedisTemplate<String, Object> redisTemplate;


    @PostMapping("/killGoods")
    public Result<String> killGoods(@RequestParam(value = "userId") Integer userId,
                                    @RequestParam(value = "productId") Integer productId) throws InterruptedException {
        // 模拟用户认证耗时操作
        Thread.sleep(200);

        // 从redis,得到商品库存
        Integer stockCount = (Integer) redisTemplate.opsForValue().get(KeyConstants.PRODUCT_HEA_KEY + productId);
        if (stockCount != null && stockCount > 0) {
            // 库存足够,进入秒杀队列
            if (queueService.kill(userId, productId)) {
                // 秒杀成功
                return Result.ok("秒杀成功");
            } else {
                // 秒杀失败
                return Result.fail("秒杀失败");
            }
        } else {
            return Result.fail("库存不足");
        }
    }

秒杀队列服务,自定义了一个线程池,用来合并用户的并发请求

private final ExecutorService secKillThreadPool;

    private final SeckillService seckillService;


    public boolean kill(Integer userId, Integer productId) {
        // 处理合并秒杀并发队列,最多等待1秒
        Future<Boolean> submit = secKillThreadPool.submit(() -> seckillService.handleSeckillLock(productId, userId));

        try {
            if (submit.isDone()) {
                Boolean b = submit.get(1, TimeUnit.SECONDS);
                log.info("秒杀结果: {}", b);
                return b;
            } else {
                log.info("秒杀失败");
                return false;
            }
        } catch (TimeoutException e) {
            log.error("秒杀超时: {}", e.getMessage());
            // 在这里可以添加适当的超时处理逻辑
            return false;
        } catch (Exception e) {
            log.error("发生异常: ", e);
            // 在这里可以添加其他异常处理逻辑
            return false;
        }
    }

秒杀服务,加入事务处理和可重用锁操作
这里说一下行级锁:
代码中,每次进入 handleSeckillLock 方法时都创建了一个新的 ReentrantLock,这将导致每个事务都拥有自己的锁,无法在不同事务之间协调锁的使用,因此无法实现预期。为了解决这个问题,可以使用数据库的行级锁机制来控制并发访问。

 @Transactional(rollbackFor = JiliException.class, propagation = Propagation.REQUIRED, isolation = Isolation.SERIALIZABLE)
    public Boolean handleSeckillLock(Integer productId, Integer userId) {
        ReentrantLock lock = new ReentrantLock();
        lock.lock();
        try {
            // 使用数据库行级锁(FOR UPDATE)来锁定产品数据,如果不用@Transactional事务会出现死锁现象和异常: Deadlock found when trying to get lock; try restarting transaction(尝试获取锁时发现死锁;尝试重新启动事务)
            Products products = this.productsService.lambdaQuery().eq(Products::getProductId, productId).last("for update").one();
            // 检查库存是否足够
            if (products.getStockQuantity() < 1) {
                return false;
            }

            // 库存扣减
            products.setStockQuantity(products.getStockQuantity() - 1);
            Integer quantity = products.getStockQuantity();
            UpdateWrapper<Products> queryWrapper = new UpdateWrapper<Products>()
                    .eq("product_id", productId)
                    .ge("stock_quantity", 1)
                    .set("stock_quantity", quantity);
            boolean updateResult = this.productsService.update(queryWrapper);
            if (updateResult) {
                // 更新缓存库存
                redisTemplate.opsForValue().decrement(KeyConstants.PRODUCT_HEA_KEY + productId);
                // 发送MQ,生成订单,这里直接操作数据库保存也行
                Orders orders = new Orders();
                orders.setUserId(Long.valueOf(userId));
                orders.setOrderStatus(2);
                orders.setOrderDate(new Date());
                boolean save = this.ordersService.save(orders);

                OrderItems orderItems = new OrderItems();
                orderItems.setOrderId(orders.getOrderId());
                orderItems.setProductId(Long.valueOf(productId));
                orderItems.setQuantity(1);
                boolean save1 = this.orderItemService.save(orderItems);
                return save && save1;
            }else{
                return false;
            }
        } catch (Exception e) {
            log.error(e.getMessage());
            return false;
        } finally {
            lock.unlock();
        }
    }

最后进行压力小测试

目前测试下来,没有发现较大的错误,可能场景不同吧,各位如果有错误可以指出