高并发下出现超卖问题

高并发下出现超卖问题

我在做秒杀项目过程中对超卖问题的一些总结

1、增加库存表判断
并发情况下,库存判断与减库存的过程有线程安全问题,所以在最终进行减库存的时候,要多增加一个判断机制,判断当前库存是否小于0,因为Update操作会有行锁,所以可却确保线程安全,这样就可以防止库存出现负数情况。

2、订单表设置联合唯一键
防止了库存为负数的情况,还需要防止一个用户重复秒杀商品的情况,可能用户同时发出多个请求秒杀商品,在并发情况下可能出现重复秒杀,所以在订单表中增加一个用户ID和商品ID组合的唯一键。

ps:刚开始我以为这样就可以万无一失了,后来在测试过程中总是出现这样的情况:库存表没有出现负数情况,可是订单表中订单数量总是超过原先设定的库存数量,刚开始很让我费解,后来思考后发现应该是事务相关的问题,就对事务细节进行了修改,完美解决问题。

3、事务细节
减库存和增加订单这两个操作需要放在同一个事务中,
不过在并发情况下,我发现在进行在没有库存的情况下,减库存操作没有执行了,但是仍然会运行增加明细操作的代码,没有在减库存失败后就终止了事务,所以我多加了一个判断机制,来判断是否进行了减库存操作,减库存成功后才进行下订单。

  • 代码展示
@Service
public class MiaoshaService {
	
	@Autowired
	GoodsService goodsService;
	
	@Autowired
	OrderService orderService;

	@Transactional
	public OrderInfo miaosha(MiaoshaUser user, GoodsVo goods) {
		//减库存 下订单 写入秒杀订单、
		int reduceCount = goodsService.reduceStock(goods);
		if(reduceCount<=0)//可能没有减库存,判断是否真的减库存了
			return null;
		return orderService.createOrder(user, goods);
	}
	
}

总结
@Transactional只有在抛出异常时才会进行回滚,减库存操作没有抛出异常,但是却并没有真的减少了库存,所以要多加一个判断,不要天真的以为加了个事务注解就万事大吉了,同时这个问题也让我懂得了测试的重要性。

<think>好的,我现在需要解决用户关于高并发秒杀场景中问题的疑问。用户提到了最佳实践,所以我得整理出有效的解决方案。首先,我需要回忆之前学过的相关知识,并结合提供的引用资料来构建回答。 用户提到的问题,主要是因为在高并发下多个请求同时扣减库存,导致库存变为负数。引用3里提到的原因是没有有效控制并发访问库存。所以,解决方案的核心应该是如何控制并发,确保库存扣减的原子性和一致性。 首先想到的是使用数据库的乐观锁,比如版本号控制。引用4中提到乐观锁,可能是指通过版本号或者CAS(Compare and Set)来更新库存,这样只有版本匹配时才更新,避免覆盖其他事务的结果。例如,在更新时检查库存是否大于0,并且版本号正确。这样可以防止多个请求同时扣减同一个库存。 然后是悲观锁,比如数据库的行级锁(SELECT FOR UPDATE)。不过引用4提到悲观锁会导致线程阻塞和排队,影响性能,特别是在高并发下可能不是最佳选择。不过在某些情况下,如果使用得当,可能还是有效,但需要权衡性能。 接下来是Redis的原子操作。因为Redis是单线程的,可以处理命令的原子性。比如使用INCR或者DECR命令来扣减库存,或者使用Lua脚本保证多个操作的原子性。引用4提到Redis的乐观锁,可能是指用WATCH命令配合事务,但更高效的方式可能是直接使用原子操作,比如DECR判断结果是否大于等于0。这样可以避免,因为Redis的操作是原子性的,不会出现并发问题。 然后,可以考虑预扣库存。将库存加载到Redis中,提前预热,每次请求先扣减Redis中的库存,如果成功再异步更新数据库。这样可以减少数据库的压力,提高响应速度。但需要保证Redis和数据库的数据一致性,可能需要消息队列来处理后续的持久化操作。 另外,限流和排队机制也很重要。比如使用令牌桶或者漏桶算法来控制请求的速率,只放行部分请求到后端系统,避免瞬间压垮系统。引用2提到瞬时高并发问题,限流可以有效缓解压力,结合队列来缓冲请求,按顺序处理。 还有分布式锁,比如基于Redis的RedLock或者ZooKeeper的分布式锁,确保同一时间只有一个服务实例能处理库存扣减。不过分布式锁的实现复杂,需要考虑锁的时和续期问题,可能会影响性能,需要谨慎使用。 最后是异步处理和队列,将请求放入消息队列,消费者逐个处理,保证顺序性。这样虽然会增加延迟,但能保证库存的正确性。同时,前端可以通过分阶段提交,比如先预占库存,再引导用户支付,支付成功后再真正扣减,否则释放预占库存。这需要设计状态机来管理订单状态。 另外,需要考虑库存回补,比如用户时未支付,库存需要自动回滚,避免占用库存导致其他用户无法购买。这可以通过定时任务或者延迟队列来实现。 总结下来,最佳实践应该是结合多种技术:Redis原子操作预扣库存、数据库乐观锁保证最终一致性、限流和队列控制并发、异步处理订单,以及库存回补机制。同时,需要压测和监控系统表现,及时调整参数。 现在要确保回答的结构清晰,分点说明每个方案,并引用相关资料。例如,在提到Redis时引用4,提到原因引用3,提到高并发场景引用1和2。需要自然地将引用标注在相关段落末尾,避免干扰阅读。</think>### 高并发秒杀场景下避免问题的最佳实践 #### 1. **库存预加载与原子操作** 将商品库存提前加载到Redis中,利用Redis的原子操作(如`DECR`或`Lua脚本`)实现库存扣减。 - **Redis原子操作示例**: ```lua -- 使用Lua脚本保证原子性 if redis.call("GET", stock_key) >= 1 then redis.call("DECR", stock_key) return 1 else return 0 end ``` 此方法通过单线程特性确保扣减操作的原子性,避免并发冲突[^4]。 #### 2. **数据库乐观锁** 在最终数据库层通过版本号或条件判断(如`WHERE stock > 0`)兜底,防止Redis与数据数据不一致时的。 - **SQL示例**: ```sql UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 123 AND stock > 0 AND version = #{current_version}; ``` 乐观锁通过版本号控制,仅允许符合条件的请求更新库存[^4]。 #### 3. **限流与削峰** - **令牌桶/漏桶算法**:限制每秒请求量,防止系统过载。 - **队列缓冲**:使用消息队列(如Kafka、RocketMQ)将请求异步化,按系统处理能力消费订单,例如: ```python # 伪代码:将请求推入队列 def handle_request(user_id, item_id): if check_redis_stock(item_id): mq.push({"user_id": user_id, "item_id": item_id}) ``` 队列机制可平滑流量峰值,避免瞬时压力[^2][^3]。 #### 4. **分布式锁控制** 对关键操作(如库存扣减)使用分布式锁(如Redis的RedLock),确保同一时间仅一个线程处理。 - **示例**: ```java // 伪代码:RedLock实现 RedissonClient redisson = Redisson.create(); RLock lock = redisson.getLock("stock_lock"); try { if (lock.tryLock(10, 30, TimeUnit.SECONDS)) { // 扣减库存操作 } } finally { lock.unlock(); } ``` 需注意锁的时和续期问题,避免死锁。 #### 5. **异步订单与状态机** - **分阶段提交**:用户提交请求后生成“预占订单”,支付成功后再转为正式订单,时未支付则释放库存。 - **库存回补**:通过定时任务或延迟队列(如Redis的ZSET)检查未支付订单,自动回滚库存。 #### 6. **熔断与降级** - **服务熔断**:当库存不足或系统压力过大时,直接返回“已售罄”或降级页面,减少无效请求。 - **静态化页面**:将秒杀商品页静态化,减少后端动态请求压力[^1]。 #### 7. **压测与监控** - **全链路压测**:模拟高并发场景,验证系统瓶颈。 - **实时监控**:通过Prometheus+Grafana监控库存、QPS、延迟等指标,及时扩容或调整策略。 ### 总结方案组合 **最佳实践流程**: 1. 前端限流(如Nginx层限制IP频率)。 2. 请求进入后,Redis原子操作扣减预加载库存。 3. 扣减成功则生成预占订单,进入消息队列。 4. 异步消费队列,通过数据库乐观锁完成最终库存扣减和订单持久化。 5. 支付时或失败时触发库存回补[^1]。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值