商品订单库存一致性问题的思考
首先先确认方案
方案1:下单后减库存;用户下单,然后库存加锁,判断库存是否充足,用户下单完成,减库存,最后释放库存锁。
方案2:支付才减库存;用户支付,然后库存加锁,判断库存是否充足,用户支付完成,减库存,最后释放库存锁。
当然还有其他方案,这里只阐述我的思考。
(库存加锁的过程有个小细节请看附1)
两种方案的比较
方案1
1)假如100个人同时下单,只有一个人能下单成功。
2)此时订单应该有一个过期状态,如果订单过期,库存加锁并回写库存后释放锁。
方案2
1)100个人可以同时下单,但是100个人同时付款时,只有一个人付款成功。
正常情况下,商品加入购物车的用户>>>下单的用户>=付款的用户。如果从库存加锁的角度来说,在下单的时候加锁,那么高并发下用户体验可能比较差,因为同时下单只有一个人能下单成功,而且服务器性能可能会比较差;下单的请求变多,那么请求加锁的次数也变多了,而支付的用户可能小于下单的用户,请求加锁的次数理论上会少不少。
我的建议是:
普通的电商项目我认为方案一就足够了,因为下单的流程简单,而支付可能涉及到很多业务,如果支付里面锁库存,考虑的东西会有点多。
但是在高并发下或者秒杀场景下,那可能就要在支付的时候锁库存。从业务角度来说,肯定是手快有,手快无;从代码的角度来说,支付跟减库存高度耦合,出现超卖、库存不一致情况大大降低,如果是下单锁库存,万一用户取消订单,那是不是库存要加回去,这种情况下高并发出现库存与实际消费不一致的可能性比较大。
实现方式以及优化
商品订单库存这个业务不仅仅只有用户下单这个功能,还要在管理后台提供商家修改库存的入口。那么这样就有两处需要用到库存,必须要考虑竞争问题。
单体架构的实现
单体架构实现这个业务是最简单的,但是性能也是最差的。
单体架构中,不管是用户操作还是管理员后台操作库存都放在里面,然后部署到机器上。此时库存锁是个全局锁,用户下单,管理员要修改库存都要从全局的库存锁拿到锁,执行完业务代码再释放。
这种单体架构就会出现一个问题,耦合度太高,一旦管理后台修改库存占用库存锁,那么用户就不能下单购买商品了。如果是购买量不多的业务,单体架构是可以满足基本需求的,这种实现成本低,易维护但不能支撑高并发。
像大部分中小型公司,一天的订单我感觉也就1000以内,单体架构完全够用了,并不需要改造成下面的方案,增加幂级的复杂度
优化方案
如果说业务增长块订单量增大,那么上面的单体架构就有局限性了。特别是现在互联网公司的架构大部分都是微服务分布式。
1)首先,每次加锁后,都需要从数据库查询库存,判断库存,然后用户下完单也要操作数据库修改库存。数据库的操作是需要时间成本的,大流量下如果其中一个用户下单时间太慢,其他用户都要等待他处理完,用户体验太差
2)其次现在架构大部分是分布式、微服务的,用户下单减库存和管理后台修改库存一般都是拆分为两个服务--下单服务和库存服务
优化的第一步。要解决下单因为操作数据库耗时过长的问题,我们可以把库存放到缓存中(一般是redis),然后对redis中的库存加redis锁,执行下单,对redis中的库存进行减库存。这么做的好处是提升了用户下单的速率,加大了并发量;其次用户下单跟管理后台的业务解耦了,为以后拆分服务做扩展。如下图:
虽然提升了性能,但是新问题出现了,数据一致性问题。现在业务是独立分开了,用户下单,redis加锁,操作redis的库存就可以了,同样管理后台修改库存,加锁操作数据库的库存就可以了。但是怎么保证这两个地方的库存一致性呢?
使用消息队列让数据库的库存进行减库存
用户下单成功后向消息队列发送一条消息,然后在管理后台业务中消费消息,进行减库存,如图:
如果保证了消息的可靠性传输,那么即可保证用户下单后的库存与数据库的库存达到最终一致性。
停售同步库存
如果管理后台要修改库存并且同步到redis上去要怎么办?可行的方案是让商家停止出售商品,然后判断redis的库存跟数据库的库存是否一致,若一致,那么商家即可修改库存,修改后更新数据库库存和redis库存。如果不一致,那么你就要知道是否管理后台还没消费完消息队列,还是其他问题,没消费完让商家等一段时间就行了,是其他问题的话程序员就妥妥的背锅吧,必须从日志系统里面查查具体是哪里出问题。
微服务分布式
其实就是把单体架构拆分,具体解决思路不变。具体要看业务把,如果订单不多的话,没必要拆分为订单服务和库存服务或者把他们解耦出来,一开始的单体架构就够用了。至于优化的方案,我感觉独角兽或者大型公司才会用到。也可能我技术角度或者业务角度没达到那种高度。
其他问题
因为之前面试,经常就问到商品库存这个问题,现在只是写个自己的思考,肯定有错误的地方。还有很多问题就不去考虑了,毕竟我没做过这个业务,例如生产上因为用户点击过快、网络问题导致的订单重复提交。
题外话
算是给点建议吧,一开始找工作尽量找稳定的公司,然后干几年积累技术。我面试的时候因为简历是3年换了3家公司,每次面试都要问我为啥经常离职。而且大公司简历几乎就进不了面试,中小型公司技术面过了,但是HR面因为这个问题也可能会把你刷了。还有就是要从业务上考虑,业务才能驱动技术的提升。(PS:广州真的是一线城市么?工资是真的低,干IT别来广州,别来广州,别来)
附1:
高并发或者秒杀场景下,不管是方案1还是方案2,如果库存为0时,是否还是每次都锁库存去走一遍流程,即库存加锁,判断库存是否充足,用户下单完成,减库存,最后释放库存锁,答案肯定是否的。
想一想java实现单例模式的代码,用了两个判断语句,那么这个场景我们也可以使用这种方式。外面先去查询一次库存,再判断是否为0,如果为0直接返回,如果不为0 ,那么库存加锁,判断库存是否充足,用户下单(或支付)完成,减库存,最后释放库存锁。以下单锁库存为例: