暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

库存扣减的那些事

光明和黑暗互相吸引 2019-11-19
1731

库存扣减的那些事

常见问题

场景

  • 平时我们网购下单,存在一些商品下单后支付成功,还有一些场景可能最后没有支付成功

问题:系统何时减库存

  • 用户下单时扣减?

  • 用户支付完成时扣减?

常见扣减方式

下单减库存

  • 用户下单成功,扣减库存

特征

  • 方式简单,控制最精确,通过数据库事务机制控制商品库存,不会出现超卖

问题

  • 正常情况下,用户下单后一般会很大概率付款,不会有大问题。但是可能会有黄牛刷单的风险,可以做到无风险疯狂占用库存

支付减库存

  • 用户下单后,并不立即扣减,而是用户完成支付后,才真正扣减库存,库存一直留给真正付款的用户


特征

  • 避免黄牛 or 恶意下单 #### 问题

  • 并发高的情况下,用户下单后可能会出现付不了款(超卖问题)

  • 超卖解决办法

    • 商家允许的情况下,通过补货来解决

    • 商家不允许的情况下,提示库存不足,不能完成支付

预扣库存(典型的预扣库存方案)

  • 用户下单后,库存保留一定的时间(比如预售:30分钟),超过时间未付款,库存自动释放,留给其他用户继续购买


特征

  • 下单时,预减库存,检测库存是否有充足

    • 充足,扣减库存

    • 不足,下单失败,提醒用户

  • 支付完成时,校验订单有效性

    • 订单未过期,支付成功

    • 订单过期,其实库存已经释放,给用户退款

问题

  • 虽然设置了库存的保留时间,但是用户可以在释放库存后再次钱柜,或者一次抢购大量库存抢占

  • 并不能彻底解决超卖问题,我们目前都是使用第三方支付,没办法在订单到期释放库存的时候一定完成了付款,在释放库存和支付之间的临界值,假如出现并发,很可能还是导致超卖,但是这种是非常极端的场景

解决办法

  • 结合风控和反作弊的措施来制止,例如:控制购买数量、未付款的情况下不能再次下单等等 ### 大型秒杀场景

  • 一般秒杀情况下,商品都是爆款(可以理解为"抢到就是赚到"),下单后不付款情况很少,同时商家对于库存有严格的限制,"下单减库存"更加合理,同时该方式对比其他两种场景来说,逻辑上更加简单,性能上占有优势。

  • 如何保障一致性?

    • 根本目的就是保证数据库中库存字段对应的数值不能小于0!

  • 常见解决方案

    • 系统中通过事务来判断,保证扣减后数据不为负数,否则回滚

    • 设置数据库字段数据为无符号证书,通过扣减是,字段值小于零触发sql语句报错

    • update stock set 剩余库存 = 当前剩余库存-扣减库存 where 当前剩余库存-扣减库存 >= 0 实现比乐观锁更乐观的一种控制

先看一个库存扣减的设计


  • 简单描述下图中的设计,先扣减缓存,缓存扣减成功扣减db,若缓存扣减成功,db扣减失败,上锁同步,如果发现库存为0,将缓存设置为-999,防止缓存穿透,如果缓存扣件失败,也直接上锁同步db

  • 如果你觉得这个还不够复杂,等等,你是不是还忽略了一个问题,这个仅仅只是库存扣减,还没有涉及退款,超时等库存的回补操作,这个设计在分布式场景下,复杂度已经很高了

具体问题具体分析

我们在库存扣减设计中核心要解决的问题是什么?

  • 库存扣减环节中,主要解决的问题是"并发写"

写在哪里?

  • 缓存 or 数据库~

缓存中扣减库存

  • 解决的是性能问题,复杂场景的关系映射不能很好支持,但是不支持事务

数据库中扣减

特性
  • 基于Mysql数据存储的特性,同一个sku的库存扣减肯定是在同一行,会有大量的线程竞争InnoDB行锁,并发越高,等待线程就越多,TPS会下降,系统的整体RT就会跟着上升,严重影响到数据库的吞吐量

问题
  • 常见的电商场景中,单个热点商品会影响整个数据库的性能,导致0.01%的商品影响99.99%商品的售卖。

解决办法
  • 热点隔离(热点商品库,非热点商品库)

    • 热点商品隔离到热点数据库中,但也带来问题,热点数据动态迁移和单独数据库维护等等问题,而且没有彻底解决并发锁的问题!!!

  • 应用层排队

    • 按照商品纬度设置队列排队,减少同一台机器对数据库同一行进行操作的并发度,同时也控制单个商品占用数据库连接的数量,防止热点商品占用太多的数据库连接

  • 数据库层排队(修改MySql源代码)

    • 机器数过多的情况下,应用层只是做到了单机排队,控制的并发能力有限。阿里数据库团队开发了针对Mysql的InnoDB层上的补丁程序(patch),可以在数据库层面对单行记录做到并发排队。

  • 其他

    • InnoDB内部存在死锁检测,MySql server 和 InnoDB的切换也会比较消耗性能,阿里数据库团队做了很多该方面的优化。COMMITONSUCCESS 和 ROLLBACKONFAIL的补丁程序,配合在SQL里面添加hint(https://blog.csdn.net/fromdw/article/details/6653882),在事务里不需要等待应用层提交(COMMIT),而在数据库执行完最后一条SQL后,直接根据TRAGETAFFECTROW的结果进行提交或回滚,可以减少网络等待时间(平均约0.7ms)。该种方式,TPS在高并发下,从原始的150飙升到8.5w,提升近566倍!最新数据未知~

库存扣减的核心到底在哪里

通过上面的分析我们可以得出一个结论

  • 在上面举出的复杂例子中,缓存起到的作用,其实跟偏向于做了一层限流,防止流量都压到数据库上

  • 库存扣减的瓶颈不在应用层面,而在于数据库层面

数据库能做哪些优化

  • I:关闭死锁检测,提高并发处理性能 对于秒杀/热卖商品这种情况,通过认真的分析,可以得出这种情况的特点,首先是很直接,其次是很暴力,正常的业务死锁会变成超时,最后是不治标,除掉老大,还有老二,问题的症结没有解决,这时候究竟该如何解决呢?经过不断努力,得出了第一种解决方法关掉死锁检测。

  • II:修改源代码,将排队提到进入引擎层前,降低引擎层面的并发度。如果请求一股脑的涌入数据库,势必会由于争抢资源造成性能下降,通过排队,让请求从混沌到有序,从而避免数据库在协调大量请求时过载。对于在固定的硬件条件下、每个系统都有一个对应的状态最优值,那么在InnoDB的线程数下,将排队队列提到进入引擎层前,这样就能够很好的解决在性能方面具有很好的提高。

  • III:组提交,降低server和引擎的交互次数,降低IO消耗,请求合并:甲买了一个商品,乙也买了同一个商品,与其把甲乙当做当做单独的请求分别执行一次商品库存减一的操作,不如把他们合并后统一执行一次商品库存减二的操作,请求合并的越多,效率提升的就越大。根据前面的排队技术,利用多线程并发下,InnoDB内部要做死锁检测等操作,会对性能影响及其严重,明确的串行事务,则server层串行,Group commit减少引擎执行次数,让性能最佳优。

  • IV:目前的一些云上数据库解决方案是一种不错的选择

写在最后

  • Redis只是解决性能问题,数据库才是解决库存一致性问题

  • 没有万能的系统,任何算法和架构都顶不住巨大的流量,限流是万精油!

  • 最后留下一个问题,上文中那个复杂的库存扣减涉及应该如何优化呢?聊聊看你的想法吧,欢迎一起讨论交流啊

文章转载自光明和黑暗互相吸引,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论