库存扣多了,到底怎么整

业务复杂、数据量大、并发量大的业务场景下,典型的互联网架构,一般会分为这么几层:

  • 调用层,一般是处于端上的browser或者APP
  • 站点层,一般是拼装html或者json返回的web-server层
  • 服务层,一般是提供RPC调用接口的service层
  • 数据层,提供固化数据存储的db

对于库存业务,一般有个库存服务,提供库存的查询、扣减、设置等RPC接口:

  • 库存查询,stock-service本质上执行的是    select num from stock where sid=$sid
  • 库存扣减,stock-service本质上执行的是    update stock set num=num-$reduce where sid=$sid
  • 库存设置,stock-service本质上执行的是    update stock set num=$num_new where sid=$sid

用户下单前,一般会对库存进行查询,有足够的存量才允许扣减:

如上图所示,通过查询接口,得到库存是5。

用户下单时,接着会对库存进行扣减:

如上图所示,购买3单位的商品,通过扣减接口,最终得到库存是2。

希望设计往往有容错机制,例如“重试”,如果通过扣减接口来修改库存,在重试时,可能会得到错误的数据,导致重复扣减:

如上图所示,如果数据库层面有重试容错机制,可能导致一次扣减执行两次,最终得到一个负数的错误库存。

重试导致错误的根本原因,是因为“扣减”操作是一个非幂等的操作,不能够重复执行,改成设置操作则不会有这个问题:

如上图所示,同样是购买3单位的商品,通过设置库存操作,即使有重试容错机制,也不会得到错误的库存,设置库存是一个幂等操作。

在并发量很大的情况下,还会有其他的问题:

如上图所示,两个并发的操作,查询库存,都得到了库存是5。

接下来用户发生了并发的购买动作(秒杀类业务特别容易出现):

如上图所示:

  • 用户1购买了3个库存,于是库存要设置为2
  • 用户2购买了2个库存,于是库存要设置为3
  • 这两个设置库存的接口并发执行,库存会先变成2,再变成3,导致数据不一致(实际卖出了5件商品,但库存只扣减了2,最后一次设置库存会覆盖和掩盖前一次并发操作)

根本原因是,设置操作发生的时候,没有检查库存与查询出来的库存有没有变化,理论上:

  • 库存为5时,用户1的库存设置才能成功
  • 库存为5时,用户2的库存设置才能成功

实际执行的时候:

  • 库存为5,用户1的set stock 2确实应该成功
  • 库存变为2了,用户2的set stock 3应该失败掉

升级修改很容易,将库存设置接口,stock-service上执行的:

update stock set num=$y where sid=$sid

升级为:

update stock set num=$num_new where sid=$sid and num=$num_old

这正是大家常说的“Compare And Set”(CAS),是一种常见的降低读写锁冲突,保证数据一致性的方法。

总结

在业务复杂,数据量大,并发量大的情况下,库存扣减容易引发数据的不一致,常见的优化方案有两个:

  • 调用“设置库存”接口,能够保证数据的幂等性
  • 在实现“设置库存”接口时,需要加上原有库存的比较,才允许设置成功,能解决高并发下库存扣减的一致性问题
时间: 2024-10-26 21:23:42

库存扣多了,到底怎么整的相关文章

读库存扣减系列文章有感

微信公众号架构师之路最近发了一篇关于库存扣减文章引起了大家的广泛转发,作为一个小菜鸟,也发表点自己的菜鸟想法吧 这篇文章原文是库存扣多了,到底怎么整 ,后面还有一篇对网友回复的解答库存扣减还有这么多方案? 第一篇文章中着重描述了扣减库存的并发问题如何解决,如何保证幂等. 文章首先解决的是如何做到幂等,因为“扣减”库存一定是一个非幂等的操作,那么可以通过“设置”库存来解决,因为设置库存是一个幂等操作. 第二个解决的是幂等之后的并发问题,因为“设置”库存虽然做到了幂等但是并没有解决并发时带来的一致性

自实现CAS原理JAVA版,模拟下单库存扣减

在做电商系统时,库存是一个非常严格的数据,根据CAS(check and swap)原来下面对库存扣减提供两种方法,一种是redis,一种用java实现CAS. 第一种 redis实现: 以下这个类是工具类,稍作修改就可运行 import java.util.regex.Pattern; import org.slf4j.Logger;import org.springframework.beans.factory.support.BeanDefinitionReader;import org.

库存扣减和订单自动失效

最近因为身体原因没怎么学习,深深的体会到身体才是最重要的.以后一定加强锻炼. 切入正题,最近项目中需要实现在线挂号功能,初步设计把排班生成的号源看做库存,挂的号看做一个个的订单,生成了订单自动锁号,十分钟不支付自动取消订单,退回号源. 排班那一套就不做详细说明了. 库存扣减和锁 初步设想有几种方案: 1.代码同步, 例如使用 synchronized,lock 等同步方法,看着貌似挺合理的. 但是synchronized 作用范围是单个jvm实例, 如果做了集群,分布式等,就没用了. 而且syn

【一起学设计模式】中介者模式+观察者模式+备忘录模式实战:(二)提交个订单我到底经历了什么鬼?

前言 再多的话就不说了,这个是接着上一讲: [一起学设计模式]状态模式+装饰器模式+简单工厂模式实战:(一)提交个订单我到底经历了什么鬼? 一起的,一些多余的赘述请先看这个篇文章. 业务场景 一图流,还是上一篇文章中一样的图,接下来我们就梳理下总结模式.观察者模式.备忘录模式的应用: 订单中心: 1.订单中心创建订单 2.订单状态流转(状态模式) 3.记录操作日志(装饰器模式+简单工厂模式) 4.订单中心通知 库存中心更新库存 调度中心: 1.库存中心更新本地库存(使用命令模式+模板方法模式+工

iDempiere 使用指南 库存出入库研究

Created by 蓝色布鲁斯,QQ32876341,blog http://www.cnblogs.com/zzyan/ iDempiere官方中文wiki主页 http://wiki.idempiere.org/zhiDempiere 中文社区www.idempiere.org.cniDempiere 中文社区QQ群 65713012 本文研究iDempiere的库存出入库机制,主要涉及到2张表格m_storageonhand, m_storagereservation以及视图m_stor

[原创]商城系统下单库存管控系列杂记(一)(并发安全和性能基础认识)

商城系统下单库存管控系列杂记(一)(并发安全和性能基础认识) 前言 参与过几个中小型商城系统的开发,随着时间的增长,以及对系统的深入研究和测试,发现确实有很多值得推敲和商榷的地方(总有很多重要细节存在缺陷).基于商城系统,无论规模大小,或者本身是否分布架构,个人觉得最核心的一环就是下单模块,而这里面更相关和棘手的一些设计和问题,大多时候都涉及库存系统.想想之前跟某人的交流,他一句"库存管控做得好,系统设计就成功了一半",自己颇有认同.围绕这个点,结合目前经验和朋友间的交流(包括近来参阅

多研究些架构,少谈些框架

论微服务架构的核心概念 微服务架构和SOA区别 微服务现在辣么火,业界流行的对比的却都是所谓的Monolithic单体应用,而大量的系统在十几年前都是已经是分布式系统了,那么微服务作为新的理念和原来的分布式系统,或者说SOA(面向服务架构)是什么区别呢? 我们先看相同点: 需要Registry,实现动态的服务注册发现机制: 需要考虑分布式下面的事务一致性,CAP原则下,两段式提交不能保证性能,事务补偿机制需要考虑: 同步调用还是异步消息传递,如何保证消息可靠性?SOA由ESB来集成所有的消息:

mq使用场景、不丢不重、时序性

mq使用场景.不丢不重.时序性.削峰 参考: http://zhuanlan.51cto.com/art/201704/536407.htm http://zhuanlan.51cto.com/art/201703/535090.htm http://zhuanlan.51cto.com/art/201704/536306.htm http://zhuanlan.51cto.com/art/201611/521602.htm http://zhuanlan.51cto.com/art/20161

多研究些架构,少谈些框架(2)-- 微服务和充血模型(转)

上篇我们聊了微服务的DDD之间的关系,很多人还是觉得很虚幻,DDD那么复杂的理论,聚合根.值对象.事件溯源,到底我们该怎么入手呢? 实际上DDD和面向对象设计.设计模式等等理论有千丝万缕的联系,如果不熟悉OOA.OOD,DDD也是使用不好的.不过学习这些OO理论的时候,大家往往感觉到无用武之地,因为大部分的Java程序员开发生涯是从学习J2EE经典的分层理论开始的(Action.Service.Dao),在这种分层理论中,我们基本没有啥机会使用那些所谓的"行为型"的设计模式,这里的核心