彻底解决超卖问题:从单体到分布式的全场景技术方案
在电商秒杀、限时促销、限量商品发售等场景中,"超卖" 是最致命的业务故障之一 ------ 明明只备货 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 个有效订单",同时结合 "库存非负校验",间接防止超卖:
- 下单时,先校验商品库存是否 > 0;
- 再尝试创建订单,唯一索引保证不会重复下单;
- 最后扣减库存(若库存不足,创建订单失败)。
实现步骤
- 订单表设计(唯一索引) :
sql
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 '订单表';
- 商品表设计(库存字段) :
sql
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 '商品表';
- 下单逻辑(Java 代码) :
scss
@Service
@Transactional
public 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),锁定商品库存行,确保 "读库存" 与 "扣库存" 的原子性:
- 下单时,用悲观锁锁定商品行,其他线程需等待锁释放;
- 读取锁定后的库存,判断是否 > 0;
- 若有库存,扣减库存并创建订单;若无库存,释放锁并返回失败。
实现步骤(核心代码)
- ProductMapper 加悲观锁查询:
sql
// ProductMapper.java
Product 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>
- 下单逻辑(悲观锁版) :
scss
@Service
@Transactional
public 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:数据库乐观锁 ------ 无锁阻塞,适合中低并发
原理
乐观锁不主动锁定数据,而是通过 "版本号" 或 "库存字段" 判断扣减前库存是否被修改:
- 下单时,读取商品库存与版本号(如库存 1000,版本 1);
- 扣减库存时,用版本号做条件(WHERE version = 1),确保期间无其他线程修改;
- 若更新成功(影响行数 = 1),说明扣减有效;若失败(影响行数 = 0),说明库存已被修改,重试或返回失败。
实现步骤(版本号机制)
- 商品表版本号字段(已在方案 1 中定义,version字段);
- 扣减库存 SQL(带版本号校验) :
xml
<!-- ProductMapper.xml 乐观锁扣减库存 -->
<update id="decreaseStockWithVersion">
UPDATE product
SET stock = stock - #{count},
version = version + 1 -- 版本号自增
WHERE id = #{productId}
AND stock > 0
AND version = #{version}; -- 关键:版本号匹配才更新
</update>
- 下单逻辑(乐观锁版,带重试) :
scss
@Service
@Transactional
public 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 分布式锁,确保 "多服务实例对同一商品的库存扣减" 互斥,实现分布式环境下的原子操作:
- 下单时,用商品 ID 生成分布式锁(如lock:product:123);
- 加锁成功后,查询 Redis 缓存库存(或数据库库存);
- 扣减库存(Redis + 数据库),释放锁;
- 加锁失败,重试或返回 "下单繁忙"。
实现步骤(基于 Redisson 分布式锁)
- 引入 Redisson 依赖(Maven) :
xml
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.3</version>
</dependency>
- Redis 缓存库存预热(秒杀前执行) :
java
@Service
public 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());
}
}
- 分布式锁下单逻辑:
scss
@Service
@Transactional
public 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 预扣减 + 消息队列异步同步" 实现 "无锁高并发":
- 秒杀前,将库存加载到 Redis(预扣减库存);
- 下单时,直接扣减 Redis 库存(原子操作,无锁),生成订单并发送到消息队列;
- 消息队列消费者异步同步库存到数据库,处理订单支付状态;
- 若用户未支付,异步回补 Redis 与数据库库存。
实现步骤(秒杀场景)
- Redis 预扣减下单(核心代码) :
scss
@Service
public 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;
}
}
- MQ 消费者异步同步库存(Kafka 消费者) :
java
@Component
public 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 库存与数据库库存,发现不一致时修正:
ini
@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. 库存上限限制
在订单创建接口添加 "总订单量≤初始备货量" 的校验,即使前面的方案失效,仍能防止超卖:
arduino
// 下单前校验总订单量
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:仅用 Redis incr/decr,不做库存非负校验
-
- 问题:redisStock.decrementAndGet()可能导致库存为负(如库存 1,两个线程同时扣减,变为 - 1);
-
- 解决:扣减后判断是否≥0,否则回补(redisStock.incrementAndGet())。
- 错误 2:分布式锁未设置过期时间
-
- 问题:服务宕机导致锁未释放,其他线程无法下单;
-
- 解决:用 Redisson 的tryLock(等待时间, 过期时间, 单位),自动释放锁。
- 错误 3:忽略 "重复下单" 问题
-
- 问题:仅防超卖,未防同一用户重复下单,导致 "一个用户买多件" 超卖;
-
- 解决:订单表加 "user_id+product_id" 唯一索引,或 Redis 记录用户下单次数(user:order:count:userId:productId)。
- 错误 4:异步同步库存无重试机制
-
- 问题:MQ 消费失败导致数据库库存未扣减,Redis 与数据库不一致;
-
- 解决:MQ 消费者添加重试机制(如重试 3 次,失败后存入死信队列人工处理)。
- 错误 5:秒杀前未预热库存到 Redis
-
- 问题:秒杀开始后,大量请求查询数据库,导致数据库宕机;
-
- 解决:秒杀前 10 分钟,将库存从数据库加载到 Redis,秒杀时优先查 Redis。
总结:超卖解决的核心逻辑
超卖解决的本质是 "在性能与一致性之间找平衡":
- 低并发场景:优先用数据库方案(乐观锁 / 悲观锁),简单可靠;
- 中高并发场景:用 Redis 分布式锁,兼顾一致性与性能;
- 高并发秒杀场景:用 Redis 预扣减 + MQ 异步同步,牺牲短暂一致性换极致性能。
无论选择哪种方案,都需记住:没有 "银弹",只有 "适合业务的方案" ------ 结合自身并发量级、一致性要求、技术栈选择方案,同时添加 "库存对账、超时回补" 等兜底措施,才能彻底杜绝超卖,保障业务稳定。