高并发场景下做库存扣减,如何避免超卖和少卖?
回答
一个典型的高并发场景下的超卖问题。
-
问题本质:
超卖问题是由并发导致,
解决该问题本质是处理并发问题。
解决的关键在于实现库存扣减过程中的原子性和有序性。
-
原子性定义:
库存查询、判断及扣减这一系列操作构成一个不可分割的原子操作,
在执行过程中不会被中断,也不允许其他线程同时执行,
以此保证操作的完整性和一致性。
-
有序性定义:
面对多个并发操作,需要让它们按照一定顺序排队执行,
避免并发冲突引发超卖等问题 。
1、数据库 扣减
-
实现方案:
这个方案实现起来非常简单。
我们可以采用加锁的方式,无论是悲观锁、还是乐观锁都可以实现的。
都能保证,扣减过程中的原子性和有序性,
-
正常来说,MySQL 的热点行更新最多也就抗 200-300 的并发更新。
-
如果,想要抗的更多呢?
-
要么就提升硬件水平。
-
要么就做一些技术改造。
-
2、Redis 扣减
-
实现原理:
利用 Redis 单线程执行特性 以及 Lua 脚本执行过程的原子性保障。
先从 Redis 取出当前剩余库存,判断是否足够扣减,
若足够,则执行扣减操作,否则返回库存不足。
由于 Lua 脚本执行时不会被打断(原子性保障),且 Redis 执行是单线程的。
所以,在脚本中,先判断,再扣减的过程能避免并发问题,实现库存扣减的原子性和有序性。
-
方案优势:
Redis 是高性能的分布式缓存,基于 Lua 脚本的扣减库存方案具有较高的执行效率。
-
Lua 脚本
3、一致性保证
-
一般,在实际应用过程中,数据库扣减 和 Redis 扣减会结合使用,进行库存扣减。
- 先在Redis中做扣减,利用Redis来抗高并发流量。
- 然后,再同步到数据库中做扣减,进行持久化存储。
-
具体实现步骤
- 先在 Redis 中,做库存扣减。
- 接着发送 MQ 消息。
- 消费者接收到消息后,在数据库中进行库存扣减和业务逻辑操作。
- 这样,我们可以保证Redis和数据库的最终一致性。
-
问题:
- 这个方案可能导致少卖。
4、少卖
-
原因分析:
Redis 中库存成功扣减,但 MQ 消息未发出,或在消息消费过程中丢失、失败等。
最后,导致数据库库存未扣减,业务未实际操作,造成 Redis 多扣库存。
-
解决思路:引入对账机制。
如:使用 zset 在 Redis 中添加流水记录,定时与数据库记录对比。
发现不一致时,及时补偿处理。
成熟电商公司通常都有此类核对系统,用于及时发现超卖、少卖问题。
5、为什么不用分布式锁?
- 上面这个方案,为什么选择用 lua 脚本实现库存扣减?为啥不用Redis 的分布式锁呢
- 喜欢的话可以 + 关,持续更新中……