秒杀场景的具体实现是后端开发中一个经典的高并发问题,考察的是如何在高并发情况下保证系统的性能和数据一致性。以下是秒杀场景的实现思路和代码示例:
秒杀场景的核心问题
- 高并发:秒杀活动会在短时间内涌入大量请求,可能导致服务器崩溃。
- 库存超卖:需要确保库存不能被超卖。
- 数据一致性:在高并发情况下,库存扣减和订单生成需要保持一致性。
- 用户体验:需要快速响应用户请求,避免长时间等待。
秒杀场景的实现步骤
1. 前端限流
在前端通过按钮灰化、验证码等方式限制用户频繁点击,减少无效请求。
2. 接口层限流
使用限流工具(如 Nginx、Guava RateLimiter)限制每秒的请求数量,保护后端服务。
3. 库存预热
在秒杀开始前,将商品库存信息加载到缓存(如 Redis)中,减少对数据库的直接访问。
4. 请求排队
使用消息队列(如 RabbitMQ、Kafka)对请求进行排队,避免直接冲击数据库。
5. 库存扣减
通过 Redis 或数据库的锁机制,确保库存扣减的原子性,避免超卖。
6. 异步处理订单
将订单生成的逻辑放入消息队列中,异步处理,提升系统响应速度。
秒杀代码实现
以下是一个基于 Redis 和 消息队列 的秒杀实现示例:
Redis 扣减库存示例
// 秒杀接口
@PostMapping("/seckill")
public String seckill(@RequestParam("userId") String userId, @RequestParam("productId") String productId) {
// 1. 检查库存是否充足
String stockKey = "stock:" + productId;
Long stock = redisTemplate.opsForValue().decrement(stockKey);
if (stock == null || stock < 0) {
return "秒杀失败,库存不足";
}
// 2. 将请求放入消息队列
SeckillOrder order = new SeckillOrder(userId, productId);
rabbitTemplate.convertAndSend("seckillQueue", order);
return "秒杀成功,订单处理中";
}消息队列处理订单示例
@RabbitListener(queues = "seckillQueue")
public void handleSeckillOrder(SeckillOrder order) {
// 1. 检查数据库库存
Product product = productRepository.findById(order.getProductId());
if (product.getStock() <= 0) {
return;
}
// 2. 扣减库存
product.setStock(product.getStock() - 1);
productRepository.save(product);
// 3. 生成订单
Order newOrder = new Order(order.getUserId(), order.getProductId());
orderRepository.save(newOrder);
}Redis 分布式锁防止超卖
String lockKey = "lock:product:" + productId;
Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(lock)) {
try {
// 执行库存扣减逻辑
} finally {
redisTemplate.delete(lockKey); // 释放锁
}
} else {
return "秒杀失败,请稍后重试";
}面试中的常见问题
-
如何防止库存超卖?
- 使用 Redis 原子操作(如
decr)。 - 数据库层面使用乐观锁或悲观锁。
- 使用分布式锁(如 Redis 或 Zookeeper)。
- 使用 Redis 原子操作(如
-
如何应对高并发?
- 前端限流、接口限流。
- 使用消息队列削峰填谷。
- 缓存库存信息,减少数据库压力。
-
如何保证数据一致性?
- 使用事务保证库存扣减和订单生成的原子性。
- 使用分布式事务框架(如 Seata)。
通过以上方法,可以实现一个高效、可靠的秒杀系统,同时满足高并发和数据一致性的需求。