来源:go-interview / Case

秒杀场景的具体实现是后端开发中一个经典的高并发问题,考察的是如何在高并发情况下保证系统的性能和数据一致性。以下是秒杀场景的实现思路和代码示例:


秒杀场景的核心问题

  1. 高并发:秒杀活动会在短时间内涌入大量请求,可能导致服务器崩溃。
  2. 库存超卖:需要确保库存不能被超卖。
  3. 数据一致性:在高并发情况下,库存扣减和订单生成需要保持一致性。
  4. 用户体验:需要快速响应用户请求,避免长时间等待。

秒杀场景的实现步骤

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 "秒杀失败,请稍后重试";
}

面试中的常见问题

  1. 如何防止库存超卖?

    • 使用 Redis 原子操作(如 decr)。
    • 数据库层面使用乐观锁或悲观锁。
    • 使用分布式锁(如 Redis 或 Zookeeper)。
  2. 如何应对高并发?

    • 前端限流、接口限流。
    • 使用消息队列削峰填谷。
    • 缓存库存信息,减少数据库压力。
  3. 如何保证数据一致性?

    • 使用事务保证库存扣减和订单生成的原子性。
    • 使用分布式事务框架(如 Seata)。

通过以上方法,可以实现一个高效、可靠的秒杀系统,同时满足高并发和数据一致性的需求。