赞
踩
业务场景:库存扣减场景,消费方调用dubbo服务,扣减订单的库存
Dubbo配置:5s超时时间,未配置默认重试次数
消费方:对商品加redis分布式锁,防止并发扣减,失效与超时时间为10s
问题:用户对相同商品的订单,同时发起扣减库存请求,造成了未扣减,或同一订单多扣减的情况
疑惑:未扣减可能是失败了,但为什么会同一订单多扣减?明明加了redis锁啊!
但问题,就出在redis锁上
1.大量相同商品扣减请求发出
2.消费方对一笔订单A的商品加锁,其他订单请求进来后redis等待
4.当订单量巨大,必然会造成订单B的redis等待超时(10s),此时dubbo请求超时(5s)
5.另一方面,由于当前大量线程不断进入并持续等待,造成cpu迟缓,当前订单A库存的操作时间变长,便可能造成扣减库存变长(7s),订单Adubbo请求超时(5s),而此时订单A仍在扣减库存
6.订单B重试2次,均未抢到锁,扣减失败
7.订单A扣减1次后,由于重试,再次扣减1次,甚至2次
经过情景复现,可以知道,由于
1.dubbo的默认重试机制
2.redis锁的等待超时
最终造成了这种情况,如何避免呢?
dubbo:
1.对非幂等性接口,dubbo不进行重试
2.对于库存扣减这种操作,消费方不关心操作结果,操作成败与否的重任交给服务方,所以dubbo请求超时时间可以定位100ms甚至更低
消费方:
1.服务方接口添加幂等性校验:如通过redis锁对订单号加为期10s的锁(时间可结合业务),尽量防止重复请求与并发
2.引入失败重试机制,对redis超时或扣减失败的订单数据进行记录,利用定时任务等机制重试1-2次。提高系统的容错性与性能
上面的复现中,我们提到了可能的重复扣减的可能性场景,但结合我所遇到的问题,依然遗留下两个问题:
1.dubbo默认重试机制是2次,加上第一次的请求,一共是3次,但我看到共有4次该订单的请求,十分不解!
2.在【复现】中模拟的重复扣减场景里,按理重复的扣减应该是间隔1-5s左右的时间间隔,但重复的扣减记录却是相同的扣减时间,难道在redis锁下,并发了?不解...
===============================================
经过将近一年的运行,业务整体平稳,但也遇到了一些新问题,并进行了解决
在这里更新记录一下主要的问题
1.
【问题:】出现了库存成功扣减为0,但库存状态仍为【正常】的情况
【复现:】多个订单对应相同系统商品去打印发货扣减商品时,会出现这种情况。比如:库存剩余8,用8个订单去打印扣减,一般都会大概率复现这个问题
【分析:】我们目前是利用redis锁,对某一个系统商品/规格锁定后进行扣减库存操作,然后再释放锁。所以反馈的用户很少(半年内两次)。
我便初步认为应该是redis锁失效导致两个线程都同时进行了库存扣减操作导致。所以对redis锁机制替换为利用setnx(之前是自己实现的redis锁机制)。
但在测试期间又出现了这种情况,那问题就不是redis锁的问题了,日志也反应出A线程操作时,B线程确实在等待,那为什么会出现两个线程扣减后状态未变更的情况呢?
答案就是:【事物】!
【解决:】扣减库存操作是先查询现有库存,然后在内存中根据库存计算出库存状态,然后再修改库存的SQL中拿这个内存计算好的库存状态去修改。这时,如果事物没控制好,就会出问题
我们项目全局开启了事物支持——@EnableTransactionManagement
【一个方法默认只有当全部执行完成后才会真正提交SQL到数据库。】
所以问题就是这个原因:即使用redis对库存修改方法加锁。在执行完库存修改方法后释放锁,但事物并不会立刻提交,而是要等到整个方法执行完才会提交。所以A线程释放锁,B线程获取锁,去查库存,此时如果A还没执行完。B线程查询到的还是A之前的库存。就会导致B计算的库存状态正确
所以我们需要重新注入service的Bean,然后再调用库存修改方法,然后对库存修改方法要指定事物传播机制为@Transactional(propagation = Propagation.REQUIRES_NEW)
当库存修改完后,立刻提交!然后再释放锁。然后B获取锁,然后去查库存时就会最新的库存。计算的库存状态就是正确的!
【总结:】这个问题本质看来是很简单,就是我在开发时没有意识到事务传播机制的问题。另外我对事物传播默认机制理解也有问题,官方文档是:【支持当前事务,如果没有事务会创建一个新的事务。】
我理解的是判断新的bean方法有没有事物,有食物就用当前bean方法的事物。恰恰相反!
是看调用这个Bean方法的A方法有没有事物,有事务就用A方法的事物。所以就算利用重新注入Bean再调方法,其实事务和外面的方法还是一个整体!果然理解很重要!要和多求证!
2.
【问题:】有用户反馈扣减库存延迟,导致用户多发货的问题
【复现:】多个订单对应一笔库存进行打印发货时,就很容易触发异步扣减库存
【分析:】因为多个订单会拆分成几组发送多个dubbo请求。到进销存后,一方面可能会触发dubbo请求限流走异步。另一方面就是在实际扣减时发生redis锁竞争导致走异步
【解决:】第一种情况比较好解决,目前dubbo请求的阀值是4,很低了,提高阀值即可。
第二种情况就要多考虑一下。
在【优化前】的锁竞争机制是:会尝试竞争5s,如果5s内持续未获取到锁,就会走异步
这种方式一般情况是满足了,但我觉得可以多提供几种可配置的锁竞争方式:
(1.)持续锁竞争,竞争失败就睡50ms,然后再次竞争,最多竞争持续10s
注释:这种情况是对应平时情况,尽可能让库存实时扣减,不走异步。如果10s都未获取到锁,那么可能redis或者代码出问题了
(2.)竞争两次,如加锁失败一次,则等待50s后再试一次。
注释:这种应对用户请求量增大或者大促期间。多提供一次机会,如果两次都失败,就走异步
(3.)竞争一次,失败就直接异步
注释:这种应对大促极限情况,尽可能的走异步。出现竞争并发就走异步。
通过这种方式,基本应对了普遍情况,但实际效果还是要看用户反馈实时跟进
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。