Springboot 并发商品秒杀压测记录
首先查出商品的库存存入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();
}
}
最后进行压力小测试
目前测试下来,没有发现较大的错误,可能场景不同吧,各位如果有错误可以指出
分类:
java 相关 / 并发压测
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗