库存扣减如何避免超卖和少卖?

本文讨论了高并发场景下防止超卖的问题,介绍了数据库加锁和乐观锁方案的优缺点,以及如何通过Redis的单线程原子性实现库存扣减,最后强调了结合Redis与数据库操作以保持一致性的方法,以及处理可能出现的少卖问题的重要性。
摘要由CSDN通过智能技术生成

🎬作者简介:大家好,我是小徐🥇
☁️博客首页:CSDN主页小徐的博客
🌄每日一句:好学而不勤非真好学者

📜 欢迎大家关注! ❤️

所谓”超卖"指的就是商品卖多了,一般我们在商品扣减库存的时候,都会先判断库存够不够如果够在进行扣减,不够则直接返回下单失败。

但是,如果在高并发场景中,可能存在以下情况

当有两个并发线程,同时查询库存,这时数据库中库存剩余1,所以两个线程都得到1的库存,然后经过库存校验之后分别开始进行库存扣减,最终导致库存被扣减成负数。

以上,就是一个典型的高并发情况下的超卖问题。

之所以会发生以上问题,主要是因为并发导致的,所以,解决超卖的问题本质上是解决并发问题。以上问题,最终就是要实现库存扣减过程中的原子性和有序性。

原子性:库存查询、库存判断以及库存扣减动作,作为一个原子操作,过程中不会被打断,也不会有其他线程执行。

有序性:多个并发操作需要排队执行。

数据库扣减

数据库中进行库存扣减是最容易想到的方案,这个方案实现起来非常简单
在扣减过程中,想要保证原子性和有序性,我们可以采用加锁的方式,无论是悲观锁、还是乐观锁都可以实现的。

但是,如果使用悲观锁来实现的话,就会导致很多请求被迫阻塞并且排队,那么如果并发请求量很大的话,就可能直接把数据库给拖垮了。

如果是乐观锁的话,可以用版本号的方式来控制有序执行,但是这个问题在于高并发场景中会存在大量的失败,而且高并发场景中也不适合使用乐观锁,因为乐观锁在update的过程中也是需要加行级锁的,也是会出现阻塞的情况。

那么,在库存扣减时,如果不加锁可以吗?

其实是可以的,我们就借助数据库自己执行引擎的顺序执行机制,只要保证库存不要扣减成负数就行了,那么可行的方案是通过SQL语句就能控制,如:

update inventory
set quantity=quantity- #{count}
where sku id='123'and quantity >= #{count}

也就是说,如果上述SQL可以执行成功的话,是可以确保库存余量大于等于0的,这就避免了超卖的发生。

但是这个方案好吗?其实是不好的。

因为这个方案本质上和乐观锁的方案缺点是一样的,都是完全依赖数据库,并且高并发情况下,多个线程同时update inventory 的时候会发生阻塞,不仅会很慢,还会把数据库拖垮的。

正常来说,MySQL的热点行更新最多也就抗200-300的并发更新,如果想要抗的更多,要么就是提升硬件水平,要么就是做一些技术改造,比如inventory hint的方式。

那么,不用数据库扣减的话,可以用什么呢?答案是可以借助缓存的扣减。

Redis扣减

我们可以基于Redis做库存扣减的,借助Redis的单线程执行的特性,再加上Lua脚本执行过程中的原子性保障,我们可以在Redis中通过Lua脚本进行库存扣减。
在Redis中,使用以下Lua脚本:

local key = KEYS[1]-- 商品的键名
local amount =tonumber(ARGV[1]) -- 扣减的数量
--获取商品当前的库存量--
local stock =tonumber(redis.call('get',key))
--如果库存足够,则减少库存并返回新的库存量
if stock >= amount then
		redis.call('decrby', key, amount)return redis.call('get', key)
else
		return "INSUFFICIENT STOCK"
end

先从Redis中取出当前的剩余库存,然后判断是否足够扣减,如果足够的话,就进行扣减,否则就返回库存不足。

因为lua脚本在执行过程中,可以避免被打断,并且redis执行的过程也是单线程的,所以在脚本中进行判断,再扣减,这个过程是可以避免并发的。所以也就可以实现前面我们说的原子性+有序性了。

并且Redis是一个高性能的分布式缓存,使用Lua脚本扣减库存的方案也非常的高效。

如何保证一致性

上面我们提到了两个方案,一个是通过数据库进行库存扣减,,一个是通过redis实现扣减,一般,在实际应用过程中,这两种方案会结合使用。

也就是说先在Redis中做扣减,利用Redis来抗高并发流量,然后再同步到数据库中,在数据库中做扣减并进行持久化存储,避免Redis挂了导致数据丢失。

一般的做法是,先在Redis中做扣减,然后发送一个MQ消息,消费者在接到消息之后做数据库中库存的真正扣减及业务逻辑操作。

这样做,我们可以保证Redis中的数据和数据库中的数据的一个最终一致性。并且也能避免超卖的发生。

但是,这个方案有个问题,就是可能导致少卖

少卖问题

假设,上面的流程中,第1步执行成功了,Redis中库存成功扣减了,但是后续第2步的消息没有发出去,或者后面的消费过程中消息丢了或者失败了等情况。

就会导致Redis中的库存被扣减了,但是数据库库存没扣减,业务的实际操作没发生。这时候的结果就是Redis中发生了多扣,那么带来的业务问题就是少卖。

那么,想要解决这类问题呢,就需要引入一些对账的机制,做一些准实时的核对,针对这类情况及时发现,如果少卖很多的话,那么就需要再把这些库存加回去。

一般在很多成熟的电商公司中,不管前面的方案做的多么完善,这个核对系统都是必不可少的。及时的核对出超卖、少卖等问题。

  • 20
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小徐很努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值