谈谈那些欠下的技术债——超卖问题

268 阅读3分钟

什么是超卖?

在并发的场景下,比如商城售卖商品中,一件商品的销售数量>库存数量的问题,称为超卖问题。

主要原因是在并发场景下,请求几乎同时到达,对库存资源进行竞争,由于没有适当的并发控制策略导致的错误。

如何避免超卖?

数据表字段设置

将库存字段设置为无符号整数,当库存扣减为负数时,数据库报错,捕获该异常并抛出即可

悲观锁

悲观锁主要用于保护数据的完整性。当多个事务并发执行时,某个事务对数据应用加锁,则其他事务只能等该事务执行完了,才能进行对该数据进行修改操作。

乐观锁

每次接收到请求时先查询版本号,然后将版本号也加入更新条件中。当更新时当前版本号与查询到的版本号不一致从而无法更新,通过失败大量请求保证数据完整性。

redis队列

将秒杀的商品id作为键,库存作为redis中的list,提前加入redis缓存中,多少件商品就入队列多少个1,高并发请求到达时依次在队列中排序获取库存,能够获得库存则继续执行下单逻辑,否则库存不足抢不到。但是这种方式下,每个请求只能购买一件商品。

Redis原子操作(Redis incr)+乐观锁

先查询redis中是否有库存信息,如果没有就去数据库查,这样就可以减少访问数据库的次数。 获取到后把数值填入redis,以商品id为key,数量为value。 还需要设置redis对应这个key的超时时间,以防所有商品库存数据都在redis中。 1.比较下单数量的大小,如果够就做后续逻辑。 2.执行redis客户端的increment,参数为负数,则做减法。

结合消息队列

  • 在系统初始化时,将商品的库存数量加载到Redis缓存中,并不是需要先请求一次才能缓存
  • 接收到秒杀请求时,在Redis中进行预减库存,当Redis中的库存不足时,直接返回秒杀失败,减少对数据库的访问。否则继续进行第3步;
  • 将请求放入异步队列(RabbitMQ) 中,立即给前端返回一个值,表示正在排队中
  • 服务端异步队列将请求出队,出队成功的请求可以然后进行秒杀逻辑,减库存–>下订单–>写入秒杀订单,成功了就返回成功。
  • 当后台订单创建成功之后可以通过websocket向用户发送一个秒杀成功通知。前端以此来判断是否秒杀成功,秒杀成功则进入秒杀订单详情,否则秒杀失败。

如何处理已发生的超卖问题?

  • 电商平台:如果超卖的商品可以补货,可以延迟发货或通知用户等待
  • 限量商品(不可补货) :需要根据规则决定如何处理,通常按下单时间支付顺序来取消超卖订单
  • 数字商品/服务:可以考虑增加库存或提供替代方案

参考资料

【并发】高并发下库存超卖问题如何解决?-阿里云开发者社区

如何解决高并发下的超卖问题? - BearBrick0 - 博客园