来源:go-interview / Case

超卖问题如何解决是面试中常见的考查点之一,尤其是在高并发场景下的库存管理中。以下是针对超卖问题的详细解答:

什么是超卖问题?

超卖问题是指在高并发场景下,多个用户同时购买同一商品时,系统允许的购买数量超过了实际库存数量。例如,库存只有 10 件商品,但由于并发请求未正确处理,系统允许 15 个用户成功下单,导致库存超卖。

超卖问题的解决方案

  1. 数据库层面的解决方案:

    • 悲观锁:
      使用数据库的锁机制(如 SELECT ... FOR UPDATE)对库存记录加锁,确保同一时间只有一个事务可以修改库存。
      示例(MySQL):

      BEGIN;
      SELECT stock FROM products WHERE id = ? FOR UPDATE;
      -- 检查库存是否足够
      UPDATE products SET stock = stock - 1 WHERE id = ?;
      COMMIT;

      缺点:性能较低,容易导致锁等待。

    • 乐观锁:
      使用版本号或时间戳控制并发更新。每次更新库存时,检查版本号是否匹配,若不匹配则重试。
      示例:

      UPDATE products 
      SET stock = stock - 1, version = version + 1 
      WHERE id = ? AND version = ?;

      缺点:在高并发场景下可能需要多次重试。

  2. 分布式锁: 在分布式系统中,可以使用 Redis 实现分布式锁,确保多个节点之间对库存的操作是串行的。
    示例(Redis 实现分布式锁):

    const lockKey = "lock:product:123";
    const lock = await redis.set(lockKey, "1", "NX", "EX", 10); // NX: 不存在时设置,EX: 过期时间
    if (lock) {
        // 操作库存
        await redis.del(lockKey); // 释放锁
    }
  3. 队列化处理: 将用户的购买请求放入消息队列(如 RabbitMQ、Kafka),由单一消费者逐一处理库存扣减操作,确保库存操作是串行的。
    优点:性能高,避免了锁竞争。
    缺点:增加了系统复杂度。

  4. 库存预扣减: 在用户下单时,先扣减库存,若用户未在规定时间内完成支付,则释放库存。
    示例流程:

    • 用户下单时,库存减 1。
    • 设置订单支付超时时间(如 15 分钟)。
    • 若超时未支付,恢复库存。
  5. 限流与降级:

    • 限流:限制每秒的请求数量,避免高并发直接冲击库存系统。
    • 降级:在库存不足时,直接返回售罄状态,避免进一步操作。

面试中的常见问题

  1. 悲观锁和乐观锁的区别?

    • 悲观锁:假设会发生并发冲突,操作前加锁,性能较低但安全性高。
    • 乐观锁:假设不会发生并发冲突,操作后校验版本号,性能较高但需要重试机制。
  2. 如何实现分布式锁?

    • 使用 Redis 的 SETNX 和 EXPIRE 实现。
    • 使用 Zookeeper 的临时节点实现。
  3. 队列化处理的优缺点?

    • 优点:避免并发问题,性能高。
    • 缺点:增加系统复杂度,可能引入延迟。

通过以上方法,可以有效解决超卖问题,具体选择哪种方案需要根据业务场景和系统架构进行权衡。