秒杀系统减库存设计!

减库存操作一般有如下几个方式。

下单减库存

付款减库存

即买家下单后,并不立即减库存,而是等到有用户付款后才真正减库存,否则库存一直保留给其他买家。

但因为付款时才减库存,如果并发比较高,有可能出现买家下单后付不了款的情况,因为可能商品已经被其他人买走了。

预扣库存

买家下单后,库存为其保留一定的时间(如10分钟),超过这个时间,库存将会自动释放,释放后其他买家就可以继续购买。

在买家付款前,系统会校验该订单的库存是否还有保留:

  • 如果没有保留,则再次尝试预扣。
  • 如果库存不足(也就是预扣失败)则不允许继续付款。
  • 如果预扣成功,则完成付款并实际地减去库存。

可能存在的问题:

下单减库存方式:

如果有竞争对手通过恶意下单的方式将该卖家的商品全部下单,让这款商品的库存减为零,商品就不能正常售卖了。

付款减库方式:

假如有100件商品,就可能出现300人下单成功的情况。

因为下单时不会减库存,所以也就可能出现下单成功数远远超过真正库存数的情况,这尤其会发生在做活动的热门商品上。

这样一来,就会导致很多买家下单成功但是付不了款,买家的购物体验自然比较差。

预扣库存方式:

针对恶意下单这种情况,虽然把有效的付款时间设置为10分钟。

但是恶意买家完全可以在10分钟后再次下单,或者采用一次下单很多件的方式把库存减完。

  • 要结合安全和反作弊的措施来制止。
  • 例如,给经常下单不付款的买家进行识别打标(可以在被打标的买家下单时不减库存)。
  • 给某些类目设置最大购买件数(例如,参加活动的商品一人最多只能买3件)。
  • 对重复下单不付款的操作进行次数限制等。

库存超卖情况:

在10分钟时间内下单的数量仍然有可能超过库存数量,遇到这种情况只能区别对待:

  • 对普通的商品下单数量超过库存数量的情况,可以通过补货来解决。
  • 但是有些卖家完全不允许库存为负数的情况,那只能在买家付款时提示库存不足。

大型秒杀中如何减库存:

参加秒杀的商品,一般都是抢到就是赚到,所以成功下单后却不付款的情况比较少。

再加上卖家对秒杀商品的库存有严格限制,所以秒杀商品采用下单减库存更加合理。

理论上由于下单减库存比预扣库存以及涉及第三方支付的付款减库存在逻辑上更为简单,所以性能上更占优势。

下单减库存在数据一致性上:

主要就是保证大并发请求时库存数据不能为负数,也就是要保证数据库中的库存字段值不能为负数,一般我们有多种解决方案:

  • 一种是在应用程序中通过事务来判断,即保证减后库存不能为负数,否则就回滚。
  • 另一种办法是直接设置数据库的字段数据为无符号整数,这样减后库存字段值小于零时会直接执行SQL语句来报错。
  • 再有一种就是使用CASE WHEN判断语句,例如这样的SQL语句:
1
>UPDATE item SET inventory = CASE WHEN inventory >= xxx THEN inventory-xxx ELSE inventory END

秒杀减库存的极致优化:

由于MySQL存储数据的特点,同一数据在数据库里肯定是一行存储(MySQL),因此会有大量线程来竞争InnoDB行锁。

而并发度越高时等待线程会越多,TPS会下降,响应时间(RT)会上升,数据库的吞吐量就会严重受影响。

解决并发锁的问题,有两种办法:

应用层做排队

  • 按照商品维度设置队列顺序执行,这样能减少同一台机器对数据库同一行记录进行操作的并发度。
  • 同时也能控制单个商品占用数据库连接的数量,防止热点商品占用太多的数据库连接。

数据库层做排队

  • 应用层只能做到单机的排队,但是应用机器数本身很多,这种排队方式控制并发的能力仍然有限。
    • 所以如果能在数据库层做全局排队是最理想的。
  • 阿里的数据库团队开发了针对这种MySQL的InnoDB层上的补丁程序,可以在数据库层上对单行记录做到并发排队。