首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >彻底解决超卖问题:从单体到分布式的全场景技术方案

彻底解决超卖问题:从单体到分布式的全场景技术方案

原创
作者头像
tcilay
发布2025-11-28 08:58:10
发布2025-11-28 08:58:10
6790
举报

彻底解决超卖问题:从单体到分布式的全场景技术方案

在电商秒杀、限时促销、限量商品发售等场景中,“超卖” 是最致命的业务故障之一 —— 明明只备货 1000 件商品,最终却卖出 1200 件,不仅导致 “无货可发” 的用户投诉,还可能引发平台信誉危机与经济损失。超卖的本质是 “并发场景下库存读写不同步”,但不同业务规模(单体 / 分布式)、不同并发量级(千级 / 万级 / 十万级)的解决方案差异极大,盲目套用 “分布式锁” 可能导致性能瓶颈,仅用 “数据库乐观锁” 又扛不住高并发。

本文结合电商实战经验,梳理超卖问题的完整解决思路,从基础的数据库控制到高并发的 Redis 预扣减,覆盖全场景方案,帮你按需选择最优解。

一、先明确:什么是超卖?为什么会发生?

1. 超卖的定义

超卖是指 “商品实际售出数量超过初始备货量”,最终导致库存为负的异常情况。典型场景:

  • 某商品初始库存 1000 件,秒杀活动中,1200 个用户成功下单,库存最终变为 - 200;
  • 用户下单后未支付,库存未及时回补,后续用户继续下单,导致总订单量超备货量。

2. 超卖的核心原因:并发下的 “库存读写冲突”

超卖的本质是 “多线程 / 多服务同时读写库存,未做同步控制”,具体可分为 3 类场景:

冲突场景

技术原因

典型案例

单体应用并发读写

多线程同时读取库存(如读取到 1000),同时扣减(均扣为 999),最终多扣 1 次

单体电商秒杀,1000 并发下单导致超卖 10 件

分布式应用数据不一致

多服务实例操作同一数据库,未做分布式同步,各实例独立扣减库存

3 个服务实例同时处理订单,库存从 1000 扣至 997,实际应扣 3 次,却扣成 997(无超卖)?不,若读取时均为 1000,扣减后均为 999,最终库存 999,超卖 2 次

异步处理库存延迟

库存扣减用异步队列,队列堆积导致 “下单成功但库存未及时扣减”,后续用户继续下单

秒杀峰值时,库存扣减 MQ 队列堆积 5 分钟,期间用户持续下单,导致超卖

关键结论:超卖的核心是 “读库存” 与 “扣库存” 两个操作非原子性 —— 若能让 “读 + 扣” 成为不可分割的原子操作,就能从根本上避免超卖。

二、基础方案:单体应用超卖解决(并发≤1000QPS)

单体应用并发较低(如日常促销,非秒杀),无需复杂中间件,仅通过数据库控制即可解决超卖,优先选择 “低侵入、易落地” 的方案。

方案 1:数据库唯一索引 —— 防止重复创建订单(间接防超卖)

原理

通过 “订单表 + 商品 ID + 用户 ID” 的唯一索引,确保 “同一用户对同一商品只能创建 1 个有效订单”,同时结合 “库存非负校验”,间接防止超卖:

  1. 下单时,先校验商品库存是否 > 0;
  2. 再尝试创建订单,唯一索引保证不会重复下单;
  3. 最后扣减库存(若库存不足,创建订单失败)。
实现步骤
  1. 订单表设计(唯一索引)
代码语言:javascript
复制
CREATE TABLE `order` (  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单ID',  `user_id` bigint NOT NULL COMMENT '用户ID',  `product_id` bigint NOT NULL COMMENT '商品ID',  `order_status` tinyint NOT NULL DEFAULT 0 COMMENT '订单状态:0=待支付,1=已支付,2=已取消',  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',  PRIMARY KEY (`id`),  -- 唯一索引:同一用户对同一商品只能创建1个待支付/已支付订单  UNIQUE KEY `uk_user_product_status` (`user_id`, `product_id`, `order_status`)   COMMENT '防止同一用户重复下单') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '订单表';
  1. 商品表设计(库存字段)
代码语言:javascript
复制
CREATE TABLE `product` (  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '商品ID',  `product_name` varchar(100) NOT NULL COMMENT '商品名称',  `stock` int NOT NULL DEFAULT 0 COMMENT '库存数量',  `version` int NOT NULL DEFAULT 0 COMMENT '版本号(乐观锁用)',  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '商品表';
  1. 下单逻辑(Java 代码)
代码语言:javascript
复制
@Service@Transactionalpublic class OrderService {    @Autowired    private OrderMapper orderMapper;    @Autowired    private ProductMapper productMapper;        /**     * 单体应用下单(唯一索引防重复下单+库存校验)     */    public OrderDTO createOrder(Long userId, Long productId) {        // 步骤1:查询商品库存(判断是否有货)        Product product = productMapper.selectById(productId);        if (product == null || product.getStock() <= 0) {            throw new BusinessException("商品已售罄");        }                // 步骤2:创建订单(唯一索引防止重复下单)        Order order = new Order();        order.setUserId(userId);        order.setProductId(productId);        order.setOrderStatus(0); // 待支付        try {            orderMapper.insert(order);        } catch (DuplicateKeyException e) {            // 唯一索引冲突,说明用户已下单            throw new BusinessException("您已下单,请勿重复购买");        }                // 步骤3:扣减库存(库存非负校验)        int updateCount = productMapper.decreaseStock(productId, 1);        if (updateCount == 0) {            // 扣减失败(可能其他线程已扣完库存),回滚事务            throw new BusinessException("商品已售罄,下单失败");        }                // 步骤4:返回订单信息        OrderDTO orderDTO = convert(order, product);        return orderDTO;    }}// ProductMapper.xml 扣减库存SQL(带库存非负校验)<update id="decreaseStock">    UPDATE product     SET stock = stock - #{count}     WHERE id = #{productId}       AND stock > 0; -- 关键:确保库存不会扣为负</update>
适用场景与优缺点
  • 适用场景:单体应用、日常促销(并发≤500QPS)、需限制 “同一用户重复下单” 的场景;
  • 优点:实现简单,无额外依赖,同时解决 “重复下单” 与 “超卖”;
  • 缺点:并发高时(如 1000QPS),唯一索引冲突频繁,用户体验差;扣库存与创建订单非原子操作,极端情况下仍可能超卖(如步骤 2 成功后,步骤 3 执行前,其他线程已扣完库存)。

方案 2:数据库悲观锁 —— 强一致性,适合低并发

原理

通过数据库的 “行锁”(SELECT ... FOR UPDATE),锁定商品库存行,确保 “读库存” 与 “扣库存” 的原子性:

  1. 下单时,用悲观锁锁定商品行,其他线程需等待锁释放;
  2. 读取锁定后的库存,判断是否 > 0;
  3. 若有库存,扣减库存并创建订单;若无库存,释放锁并返回失败。
实现步骤(核心代码)
  1. ProductMapper 加悲观锁查询
代码语言:javascript
复制
// ProductMapper.javaProduct selectByIdForUpdate(Long productId);// ProductMapper.xml(悲观锁查询)<select id="selectByIdForUpdate" resultType="com.example.Product">    SELECT id, product_name, stock, version     FROM product     WHERE id = #{productId}     FOR UPDATE; -- 行锁:锁定该商品行,其他线程无法修改</select>
  1. 下单逻辑(悲观锁版)
代码语言:javascript
复制
@Service@Transactionalpublic class OrderService {    public OrderDTO createOrderWithPessimisticLock(Long userId, Long productId) {        // 步骤1:悲观锁查询商品(锁定行,其他线程等待)        Product product = productMapper.selectByIdForUpdate(productId);        if (product == null || product.getStock() <= 0) {            throw new BusinessException("商品已售罄");        }                // 步骤2:扣减库存(无需额外校验,锁已保证原子性)        int updateCount = productMapper.decreaseStock(productId, 1);        if (updateCount == 0) {            throw new BusinessException("下单失败");        }                // 步骤3:创建订单(可加唯一索引防重复下单)        Order order = new Order();        order.setUserId(userId);        order.setProductId(productId);        orderMapper.insert(order);                return convert(order, product);    }}
适用场景与优缺点
  • 适用场景:单体应用、低并发(≤300QPS)、对数据一致性要求极高的场景(如奢侈品秒杀);
  • 优点:强一致性,100% 避免超卖,无需担心并发冲突;
  • 缺点:悲观锁会阻塞其他线程,并发高时导致 “订单创建排队”,响应时间变长(如 1000QPS 下,响应时间从 100ms 升至 500ms)。

方案 3:数据库乐观锁 —— 无锁阻塞,适合中低并发

原理

乐观锁不主动锁定数据,而是通过 “版本号” 或 “库存字段” 判断扣减前库存是否被修改:

  1. 下单时,读取商品库存与版本号(如库存 1000,版本 1);
  2. 扣减库存时,用版本号做条件(WHERE version = 1),确保期间无其他线程修改;
  3. 若更新成功(影响行数 = 1),说明扣减有效;若失败(影响行数 = 0),说明库存已被修改,重试或返回失败。
实现步骤(版本号机制)
  1. 商品表版本号字段(已在方案 1 中定义,version字段);
  2. 扣减库存 SQL(带版本号校验)
代码语言:javascript
复制
<!-- ProductMapper.xml 乐观锁扣减库存 --><update id="decreaseStockWithVersion">    UPDATE product     SET stock = stock - #{count},        version = version + 1 -- 版本号自增    WHERE id = #{productId}       AND stock > 0       AND version = #{version}; -- 关键:版本号匹配才更新</update>
  1. 下单逻辑(乐观锁版,带重试)
代码语言:javascript
复制
@Service@Transactionalpublic class OrderService {    // 最大重试次数(避免无限循环)    private static final int MAX_RETRY_COUNT = 3;        public OrderDTO createOrderWithOptimisticLock(Long userId, Long productId) {        int retryCount = 0;        while (retryCount < MAX_RETRY_COUNT) {            // 步骤1:查询商品(含版本号)            Product product = productMapper.selectById(productId);            if (product == null || product.getStock() <= 0) {                throw new BusinessException("商品已售罄");            }                        // 步骤2:乐观锁扣减库存            int updateCount = productMapper.decreaseStockWithVersion(                productId, 1, product.getVersion()            );                        if (updateCount > 0) {                // 步骤3:扣减成功,创建订单                Order order = new Order();                order.setUserId(userId);                order.setProductId(productId);                orderMapper.insert(order);                return convert(order, product);            }                        // 步骤4:扣减失败,重试(间隔10ms,避免CPU空转)            retryCount++;            try {                Thread.sleep(10);            } catch (InterruptedException e) {                Thread.currentThread().interrupt();                break;            }        }                // 重试次数用尽,返回失败        throw new BusinessException("下单人数过多,请稍后重试");    }}
适用场景与优缺点
  • 适用场景:单体应用、中低并发(≤1000QPS)、对响应时间敏感的场景(如日常电商促销);
  • 优点:无锁阻塞,并发性能优于悲观锁;实现简单,仅需数据库字段;
  • 缺点:重试机制可能导致部分用户下单失败(需友好提示);高并发下重试次数增加,响应时间变长。

三、进阶方案:分布式应用超卖解决(并发≤10000QPS)

分布式应用(多服务实例、多数据库分库分表)或中高并发(如秒杀 QPS=5000)场景,数据库方案已无法支撑,需引入 Redis、消息队列等中间件,通过 “缓存预扣减 + 异步同步” 提升性能,同时保证不超卖。

方案 4:Redis 分布式锁 —— 分布式强一致性

原理

通过 Redis 分布式锁,确保 “多服务实例对同一商品的库存扣减” 互斥,实现分布式环境下的原子操作:

  1. 下单时,用商品 ID 生成分布式锁(如lock:product:123);
  2. 加锁成功后,查询 Redis 缓存库存(或数据库库存);
  3. 扣减库存(Redis + 数据库),释放锁;
  4. 加锁失败,重试或返回 “下单繁忙”。
实现步骤(基于 Redisson 分布式锁)
  1. 引入 Redisson 依赖(Maven)
代码语言:javascript
复制
<dependency>    <groupId>org.redisson</groupId>    <artifactId>redisson-spring-boot-starter</artifactId>    <version>3.23.3</version></dependency>
  1. Redis 缓存库存预热(秒杀前执行)
代码语言:javascript
复制
@Servicepublic class StockPreloadService {    @Autowired    private RedissonClient redissonClient;    @Autowired    private ProductMapper productMapper;        /**     * 库存预热:将数据库库存加载到Redis(秒杀前执行)     */    public void preloadStockToRedis(Long productId) {        // 1. 查询数据库库存        Product product = productMapper.selectById(productId);        if (product == null) {            throw new BusinessException("商品不存在");        }                // 2. 存储到Redis(key=stock:product:123,value=库存数)        RAtomicLong redisStock = redissonClient.getAtomicLong("stock:product:" + productId);        redisStock.set(product.getStock());    }}
  1. 分布式锁下单逻辑
代码语言:javascript
复制
@Service@Transactionalpublic class DistributedOrderService {    @Autowired    private RedissonClient redissonClient;    @Autowired    private ProductMapper productMapper;    @Autowired    private OrderMapper orderMapper;        public OrderDTO createOrderWithDistributedLock(Long userId, Long productId) {        // 步骤1:获取分布式锁(商品ID为锁key,过期时间30秒,自动释放)        RLock lock = redissonClient.getLock("lock:product:" + productId);        try {            // 尝试加锁,最多等待5秒,5秒内未获取到锁则失败            boolean locked = lock.tryLock(5, 30, TimeUnit.SECONDS);            if (!locked) {                throw new BusinessException("下单人数过多,请稍后重试");            }                        // 步骤2:查询Redis库存(减少数据库访问)            RAtomicLong redisStock = redissonClient.getAtomicLong("stock:product:" + productId);            long currentStock = redisStock.get();            if (currentStock <= 0) {                throw new BusinessException("商品已售罄");            }                        // 步骤3:扣减Redis库存(原子操作)            boolean decrSuccess = redisStock.decrementAndGet() >= 0;            if (!decrSuccess) {                // 若扣减后为负,回补Redis库存(避免超卖)                redisStock.incrementAndGet();                throw new BusinessException("商品已售罄");            }                        // 步骤4:扣减数据库库存(最终一致性,可异步)            int updateCount = productMapper.decreaseStock(productId, 1);            if (updateCount == 0) {                // 数据库扣减失败,回补Redis库存                redisStock.incrementAndGet();                throw new BusinessException("下单失败");            }                        // 步骤5:创建订单(唯一索引防重复下单)            Order order = new Order();            order.setUserId(userId);            order.setProductId(productId);            try {                orderMapper.insert(order);            } catch (DuplicateKeyException e) {                // 重复下单,回补Redis和数据库库存                redisStock.incrementAndGet();                productMapper.increaseStock(productId, 1); // 库存回补SQL                throw new BusinessException("您已下单,请勿重复购买");            }                        return convert(order, productMapper.selectById(productId));        } catch (InterruptedException e) {            Thread.currentThread().interrupt();            throw new BusinessException("下单异常,请重试");        } finally {            // 步骤6:释放锁(确保锁一定释放)            if (lock.isHeldByCurrentThread()) {                lock.unlock();            }        }    }}
适用场景与优缺点
  • 适用场景:分布式应用、中高并发(≤10000QPS)、对一致性要求高的场景(如电商秒杀);
  • 优点:分布式环境下强一致,支持较高并发;Redis 库存查询减少数据库压力;
  • 缺点:依赖 Redis 集群(需保证 Redis 高可用);锁粒度为商品 ID,热门商品可能出现 “锁竞争”(可通过商品分片优化)。

方案 5:Redis 预扣减 + 消息队列异步同步 —— 高并发秒杀(QPS≥10000)

原理

高并发秒杀(如 QPS=10 万)场景下,Redis 分布式锁的锁竞争会成为瓶颈,需用 “Redis 预扣减 + 消息队列异步同步” 实现 “无锁高并发”:

  1. 秒杀前,将库存加载到 Redis(预扣减库存);
  2. 下单时,直接扣减 Redis 库存(原子操作,无锁),生成订单并发送到消息队列;
  3. 消息队列消费者异步同步库存到数据库,处理订单支付状态;
  4. 若用户未支付,异步回补 Redis 与数据库库存。
实现步骤(秒杀场景)
  1. Redis 预扣减下单(核心代码)
代码语言:javascript
复制
@Servicepublic class SeckillOrderService {    @Autowired    private RedissonClient redissonClient;    @Autowired    private KafkaTemplate<String, String> kafkaTemplate;    @Autowired    private OrderMapper orderMapper;        /**     * 秒杀下单:Redis预扣减+MQ异步同步     */    public OrderDTO seckillOrder(Long userId, Long productId) {        // 步骤1:Redis原子扣减库存(无锁,高并发)        RAtomicLong redisStock = redissonClient.getAtomicLong("stock:product:" + productId);        long remainingStock = redisStock.decrementAndGet();        if (remainingStock < 0) {            // 库存不足,回补Redis(避免超卖)            redisStock.incrementAndGet();            throw new BusinessException("手慢了,商品已售罄");        }                // 步骤2:生成订单(仅存必要信息,快速返回)        Order order = new Order();        order.setUserId(userId);        order.setProductId(productId);        order.setOrderStatus(0); // 待支付        String orderNo = generateOrderNo(); // 生成唯一订单号        order.setOrderNo(orderNo);        orderMapper.insertSelective(order); // 仅插入必要字段,提升速度                // 步骤3:发送MQ消息(异步同步库存到数据库+处理支付)        SeckillMqMsg msg = new SeckillMqMsg();        msg.setOrderNo(orderNo);        msg.setProductId(productId);        msg.setUserId(userId);        kafkaTemplate.send("seckill-order-topic", JSON.toJSONString(msg));                // 步骤4:快速返回订单信息(用户无需等待库存同步完成)        OrderDTO orderDTO = new OrderDTO();        orderDTO.setOrderNo(orderNo);        orderDTO.setStatus("待支付");        orderDTO.setMsg("下单成功,请在15分钟内支付");        return orderDTO;    }        // 生成唯一订单号(时间戳+随机数+用户ID后4位)    private String generateOrderNo() {        return new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())                 + RandomUtils.nextInt(1000, 9999)                 + Thread.currentThread().getId() % 10000;    }}
  1. MQ 消费者异步同步库存(Kafka 消费者)
代码语言:javascript
复制
@Componentpublic class SeckillOrderConsumer {    @Autowired    private ProductMapper productMapper;    @Autowired    private OrderMapper orderMapper;    @Autowired    private RedissonClient redissonClient;        @KafkaListener(topics = "seckill-order-topic")    public void processSeckillOrder(String msgStr) {        SeckillMqMsg msg = JSON.parseObject(msgStr, SeckillMqMsg.class);        String orderNo = msg.getOrderNo();        Long productId = msg.getProductId();                try {            // 步骤1:异步扣减数据库库存(最终一致性)            int updateCount = productMapper.decreaseStock(productId, 1);            if (updateCount == 0) {                // 数据库库存不足(极端情况,Redis预扣减有误),回补Redis库存                RAtomicLong redisStock = redissonClient.getAtomicLong("stock:product:" + productId);                redisStock.incrementAndGet();                // 更新订单状态为“下单失败”                orderMapper.updateStatusByOrderNo(orderNo, 3); // 3=下单失败                log.error("异步扣减库存失败,订单号:{},商品ID:{}", orderNo, productId);                return;            }                        // 步骤2:监听支付状态(如15分钟未支付,回补库存)            listenPaymentStatus(orderNo, productId);        } catch (Exception e) {            log.error("处理秒杀订单异常,订单号:{}", orderNo, e);            // 异常时回补Redis库存            RAtomicLong redisStock = redissonClient.getAtomicLong("stock:product:" + productId);            redisStock.incrementAndGet();            orderMapper.updateStatusByOrderNo(orderNo, 3);        }    }        // 监听支付状态:15分钟未支付,回补库存    private void listenPaymentStatus(String orderNo, Long productId) {        // 用定时任务或延迟队列监听支付状态(如RabbitMQ延迟队列)        // 若15分钟未支付:        // 1. 回补Redis库存:redisStock.incrementAndGet()        // 2. 回补数据库库存:productMapper.increaseStock(productId, 1)        // 3. 更新订单状态:orderMapper.updateStatusByOrderNo(orderNo, 2) // 2=已取消    }}
适用场景与优缺点
  • 适用场景:高并发秒杀(QPS≥10000)、对响应时间要求极高的场景(如 “双十一” 秒杀);
  • 优点:无锁设计,支持极高并发;异步同步降低响应时间(下单响应时间 < 100ms);
  • 缺点:Redis 与数据库存在短暂不一致(需最终一致性);需处理 “未支付回补库存” 的复杂逻辑;依赖 MQ 与 Redis 的高可用。

四、终极保障:超卖防护的 3 个兜底措施

无论选择哪种方案,都需添加 “兜底措施”,应对极端情况(如 Redis 宕机、MQ 队列堆积):

1. 库存对账机制

定时(如每 5 分钟)对比 Redis 库存与数据库库存,发现不一致时修正:

代码语言:javascript
复制
@Scheduled(fixedRate = 300000) // 每5分钟执行一次public void checkStockConsistency() {    // 1. 查询所有秒杀商品    List<Product> seckillProducts = productMapper.listSeckillProducts();    for (Product product : seckillProducts) {        Long productId = product.getId();        int dbStock = product.getStock();                // 2. 查询Redis库存        RAtomicLong redisStock = redissonClient.getAtomicLong("stock:product:" + productId);        long redisStockVal = redisStock.get();                // 3. 对比并修正(以数据库为准,或根据业务规则)        if (dbStock != redisStockVal) {            log.warn("库存不一致,商品ID:{},数据库库存:{},Redis库存:{}",                      productId, dbStock, redisStockVal);            // 修正Redis库存为数据库库存            redisStock.set(dbStock);        }    }}

2. 库存上限限制

在订单创建接口添加 “总订单量≤初始备货量” 的校验,即使前面的方案失效,仍能防止超卖:

代码语言:javascript
复制
// 下单前校验总订单量int totalOrderCount = orderMapper.countByProductId(productId);int initialStock = productMapper.selectInitialStock(productId); // 初始备货量if (totalOrderCount >= initialStock) {    throw new BusinessException("商品已售罄");}

3. 支付超时回补

用户下单后未支付(如 15 分钟超时),必须回补库存,避免 “占库存不付款” 导致的超卖:

  • 用延迟队列(如 RabbitMQ 延迟交换机)监听订单支付状态;
  • 超时未支付,执行 “库存回补”(Redis + 数据库)与 “订单取消”。

五、方案选型速查表

业务规模

并发量级

推荐方案组合

核心优势

单体应用(中小电商)

≤500QPS

数据库乐观锁 + 唯一索引

无额外依赖,易落地

单体应用(促销活动)

500-1000QPS

数据库乐观锁 + Redis 缓存库存

兼顾性能与一致性

分布式应用(多服务)

1000-10000QPS

Redis 分布式锁 + 数据库同步

分布式强一致,支持中高并发

高并发秒杀(大促)

≥10000QPS

Redis 预扣减 + MQ 异步同步 + 库存对账

无锁高并发,响应时间短

六、避坑指南:超卖解决的 5 个常见错误

  1. 错误 1:仅用 Redis incr/decr,不做库存非负校验
    • 问题:redisStock.decrementAndGet()可能导致库存为负(如库存 1,两个线程同时扣减,变为 - 1);
    • 解决:扣减后判断是否≥0,否则回补(redisStock.incrementAndGet())。
  2. 错误 2:分布式锁未设置过期时间
    • 问题:服务宕机导致锁未释放,其他线程无法下单;
    • 解决:用 Redisson 的tryLock(等待时间, 过期时间, 单位),自动释放锁。
  3. 错误 3:忽略 “重复下单” 问题
    • 问题:仅防超卖,未防同一用户重复下单,导致 “一个用户买多件” 超卖;
    • 解决:订单表加 “user_id+product_id” 唯一索引,或 Redis 记录用户下单次数(user:order:count:userId:productId)。
  4. 错误 4:异步同步库存无重试机制
    • 问题:MQ 消费失败导致数据库库存未扣减,Redis 与数据库不一致;
    • 解决:MQ 消费者添加重试机制(如重试 3 次,失败后存入死信队列人工处理)。
  5. 错误 5:秒杀前未预热库存到 Redis
    • 问题:秒杀开始后,大量请求查询数据库,导致数据库宕机;
    • 解决:秒杀前 10 分钟,将库存从数据库加载到 Redis,秒杀时优先查 Redis。

总结:超卖解决的核心逻辑

超卖解决的本质是 “在性能与一致性之间找平衡”:

  • 低并发场景:优先用数据库方案(乐观锁 / 悲观锁),简单可靠;
  • 中高并发场景:用 Redis 分布式锁,兼顾一致性与性能;
  • 高并发秒杀场景:用 Redis 预扣减 + MQ 异步同步,牺牲短暂一致性换极致性能。

无论选择哪种方案,都需记住:没有 “银弹”,只有 “适合业务的方案”—— 结合自身并发量级、一致性要求、技术栈选择方案,同时添加 “库存对账、超时回补” 等兜底措施,才能彻底杜绝超卖,保障业务稳定。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 彻底解决超卖问题:从单体到分布式的全场景技术方案
    • 一、先明确:什么是超卖?为什么会发生?
      • 1. 超卖的定义
      • 2. 超卖的核心原因:并发下的 “库存读写冲突”
    • 二、基础方案:单体应用超卖解决(并发≤1000QPS)
      • 方案 1:数据库唯一索引 —— 防止重复创建订单(间接防超卖)
      • 方案 2:数据库悲观锁 —— 强一致性,适合低并发
      • 方案 3:数据库乐观锁 —— 无锁阻塞,适合中低并发
    • 三、进阶方案:分布式应用超卖解决(并发≤10000QPS)
      • 方案 4:Redis 分布式锁 —— 分布式强一致性
      • 方案 5:Redis 预扣减 + 消息队列异步同步 —— 高并发秒杀(QPS≥10000)
    • 四、终极保障:超卖防护的 3 个兜底措施
      • 1. 库存对账机制
      • 2. 库存上限限制
      • 3. 支付超时回补
    • 五、方案选型速查表
    • 六、避坑指南:超卖解决的 5 个常见错误
    • 总结:超卖解决的核心逻辑
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档