补充上个帖子的分布式事务之扣减库存

2021年6月28日 2点热度 0条评论 来源: myth_gy

       当业务规模不大,并且对于生成订单并冻结库存等操作要求一致性较高时,比较推荐ACID数据库进行操作,加入缓存或消息队列后复杂度以及实时一致性较差;

       比如有如下场景:

步骤 相关业务
1 冻结库存(保证下单时有足够的库存)
2 生成对应的订单
3 支付订单,扣减冻结库存

 

 

 

 

 

(1)该场景如果仅使用ACID数据库进行控制,则伪代码为:

@Transactional(rollbackFor = Exception.class)
public Result placeOrder(xxx xxx){
     //直接进行冻结扣减库存
     boolean b = deductionInventory(xxx);
     if(!b){
       //抛出异常中断
     }
     //进行订单生成等其他业务操作;
}

以上代码能够应付一般的并发下单了,但是如果涉及到下单有多个商品,在扣除库存前有其他事务(例如需要扣除积分,但是积分操作只涉及用户个人),那么整个方法将是一个大事务,相关的库存行锁将占用时间较长,影响了其他用户的下单;

(2)根据以上问题,优化一下,将扣除库存及生成订单的操作提取出来,并且使用REQUIRES_NEW事务传播类型,旨在完成冻结库存成功后,结束当前事务操作,进行后续耗时操作;

@Transactional(rollbackFor = Exception.class)
public Result placeOrder(xxx xxx){
     //相关的用户积分业务事务操作,此时只锁住该用户积分行
     userservice.do(xxx);
        
     //新建独立事务扣除库存及生成订单,成功后直接释放商品库存行锁;当出现不足等可跟积分事务一起回滚
     //@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
     boolean b = newService.deductionInventoryAndPlaceOrder(xxx);
     if(!b){
       //抛出异常中断,并回滚积分业务
     }
   
}

(3)其实在冻结库存时可能还会出现个问题,就是可能会出现MySQL的循环占用资源死锁的问题,具体相关概念可以去其他地方查询,我这里给出的最简单的方法就是将用于扣减库存的商品列表,使用某一字段进行排序避免;此方式同样适用于调度任务在处理批量回滚商品库存等业务中;

(4)比如我们想在该下单方式的基础上,支持RPC调用,例如商品是单独服务,订单又是单独服务,如何保证一致性:

@Transactional(rollbackFor = Exception.class)
public Result placeOrder(xxx xxx){
     //直接进行冻结扣减库存,并且存储冻结库存订单
     boolean b = deductionAndSaveInventory(xxx);
     //deductionAndSaveInventory方法包括以下操作,均为本地事务操作:
     //扣减库存,保存冻结库存的日志

     if(!b){
       //抛出异常中断
     }

     //进行订单生成等其他业务操作;
     boolean b = RPC.createOrder(xxx);
     //生成订单业务包括:生成订单,删除冻结库存日志;
}


task:根据生成时间等条件查询冻结库存日志,进行回滚;

 以上方式,可能会存在下单失败,但是暂时将库存扣除的问题,这就要合理的规划调度回滚库存以及根据具体业务场景选择下单方式;

(5)其他的下单方式,例如:

1)在付款时在进行扣减库存,判断是否可以支付;

2)不在意库存扣为负数,或者有其他方式避免库存不足,不需要判断库存,则直接可以下单成功使用消息队列等方式,进行最终一致性的重试;

    原文作者:myth_gy
    原文地址: https://blog.csdn.net/myth_g/article/details/108997272
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。