1、分布式事务的常用解决方案
(1)、基于数据库XA/JTA协议的方式;(需要数据库厂商的支持;java组件有atomikos等)
(2)、异步校对数据的方式;(支付宝、微信支付主动查询支付转态、对账单的形式)
(3)、基于可靠消息(MQ)的解决方案;(异步场景;通用性强;拓展性较高)
(4)、TCC编程式解决方案。(严选、阿里、蚂蚁金服自己封装的DTX)
2、使用rabbitmq解决分布式事务
(1)、整体设计思路
要求:①、可靠生产:保证消息一定要发送到rabbitmq服务中
②、可靠消费:保证消息取出来一定正确消费掉。
最终多方数据达到一致性。
发送消息到rabbitmq之后的回调方法修改消息发送的状态
设置发送成功的回调配置:spring.rabbitmq.publisher-confirm-type=CORRELATED
private static boolean IS_END = false; @Autowired private RabbitTemplate rabbitTemplate; @PostConstruct public void setUp(){ System.out.println("回调方法"); //回调方法
rabbitTemplate.setConfirmCallback((correlationData,ack,cause)->{ if (!ack){ //TODO 执行失败的逻辑
System.out.println("执行失败:"+cause); }else{ //TODO 执行成功的逻辑
System.out.println("执行成功"); } }); IS_END = true; } @Test public void test0(){ //发送消息
rabbitTemplate.convertAndSend("OrderDispathExchange",null,"测试sa"); System.out.println("发送完成"); do{}while (!IS_END); }
备注:如果出现回执没有收到或者状态修改失败等特殊情况,可以采用一下方案:定时检查消息表,超时没有发送成功的,再次发送。
幂等:根据ID或业务数据,判断数据是否重复(可以查看对应的数据表),重复则忽略。
消费者正常消费后,返回ACK状态到rabbitMq的服务端,进行数据的清除
消费者处理消息失败之后,需要MQ再次重发给消费者(一般会重试几次,由消费者自身记录重试次数,并进行次数控制)。
开启手动ack,控制消息在MQ中的删除、重发、丢弃
spring.rabbitmq.listener.simple.acknowledge-mode=manual
/** * * @param message 消息内容 * @param channel java和mq建立的连接通道 * @param tag 当前消息的标签 * @throws Exception */ @RabbitListener(queues = "OrderDispath") public void messageConsumer(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG)long tag)throws Exception{ try { //取出信息,执行相关业务,同一个数据不同多次处理,可以根据业务情况去重, // 保证幂等性(如:可以使用readis记录每条数据处理次数或者使用主键进行数据库的插入)
System.err.println(message); //..... //ack -反馈给mq,数据已经正常处理
channel.basicAck(tag,false); }catch (Exception e){ //捕获异常,通知mq重发 //但是一定要记录该条消息处理的次数,防止重试多次,导致死循环 //对于重试多次,或者明确不在继续重发的异常,通知mq丢弃,通过运维告急机制,通知人工处理
channel.basicNack(tag,false,false); } //生产环境,重要的数据出现异常,必须人工干预 //如果不回复,就等这个consumer断开连接之后,mq-server会继续推送信息
}
3、方案优缺点
优点:通用性强,拓展性强,方案成熟。
缺点:基于消息中间件,只适合异步场景,不支持事务回滚(要求必须成功);消息处理会有延迟,需要业务上能够容忍。
备注:尽量避免分布式事务;尽量将非核心的事务做成异步。