本人菜鸟一枚,以下是个人解决详细分析和方案,希望大牛们指点不足或者存在的问题,万分感谢!
此方案达到的效果:数据最终一致性(允许实时查询多表数据),数据不丢失。
一:分布式事物场景
在用户下单的时候,会执行俩步骤: A:生成订单 B: 库存扣减 A 和B不在同一个库中,当代码Bug或者服务器环境问题,网络等问题,就可能出现以下异常情况:
1:可能A执行成功,B执行失败
2:B 执行成功, A执行失败()(使用线程池分别执行订单和库存操作,可能存在B成功,A失败)
3:AB多失败
很多人可能的解决方案是直接在try{}catch{} 进行回滚,回滚可能出现的问题:
1:数据丢失,有些公司是不允许的,这样订单就会少很多
2:服务器,程序bug, 网络环境问题等奇葩问题,可能偶尔会发生
3: 回滚失败
二:解决实现详细方案(Rabbitmq + redision + 任务调度)
1:在事物库创建tb_transcation 核心字段 (id, body, status, mq_consume_status, mq_send_status, cause, result)
2 : 在A库(订单库) 创建tb_order_transcation(id, order_id, transcation_id, body, status)
3: 在B库(库存库)创建tb_stock_transcation(库存日志表也可以称事物表)(id, stock_id, transcation_id, body, status)(这种表的作用:防止多个用户同时修改同一个数据,如果不存这个记录,那么如果有一个用户操作失败,那么库存数就会存在问题,这里要注意排序问题)
4:生产端处理:
1: 封装前会效验库存和订单信息,封装主事物表body数据(body = orderdata + stockData),status(此时是待执行阶段), 将这个事物po存入事物表
2:发送MQ 消息,消息主体为transcationId, 这样消息小,节省空间,传输性能快。
5:消费端处理:
1:执行生成订单操作(生成订单接口执行订单事物保存操作, 这个必须在同一个服务里面,同一个库,这样可以通过Spring进行错误回滚,不会导致生成订单成功,而订单事物保存失败,可以达到一致性)
2:执行扣除库存操作(库存接口执行库存事物保存操作)
3:如果1,2步执行成功,name更改tb_transcation的状态(更改成功,结束操作)
4:如果1,2,3步其中一步执行出错,那么try{} catch 里面 更改tb_transcation的状态(执行失败状态),如果更改出错,那么重试3次(可以设置),重试需要查询多个表的事物状态(tb_order_transcation和tb_stock_transcation)状态,如果状态多正常,那么只需要更改事物状态,否则重新执行第1或者2步(如果N次重试还是失败,那么不是代码问题,或者就是服务器问题, 那么丢弃这个MQ, 后台开发一个事物列表查看失败原因,可以提供按钮执行,达到数据最终一致性,或者通过分布式定时器程序达到最终一致性)
注意:生成订单和库存的时候可能会造成重复数据,如果存在多个消费者的话,如果多个获取到tb_order_transcation的状态多是失败状态,那么就会重复生成订单,虽然id会冲突,生成不了,这样打印很多错误日志。而且如果是库存扣减的话,这样库存就会出现问题,所以这里最好加分布式锁防止并发问题
网友评论