背景:
业务场景:库存扣减场景,消费方调用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锁下,并发了?不解...
如果有大佬看到此处,麻烦提出您的意见和建议,感谢?....