通过RabitMQ实现分布式事务 高焕堂《嵌入式UML设计》读书笔记_第一章 2020年中国5G在物联网行业:中国通信运营商5G投资达1.23万亿元(可下载) 面试题精选:数据伪造 繁忙的都市(并查集、Kruskal) BFS 力扣 200.岛屿数量 深度操作系统Deepin V20正式版 2020-09-13 【OS】Bankers Algorithm 用于调用的参数太少/写入位置时发生冲突/检测到无效的异常处理程序例程 后渗透之各种维持权限的后门原理即利用 MIPS Branch Target Buffer动态分支预测(BTB) Oracle实战优化:INSERT ALL关键字的应用 Linux中MySQL数据库的使用②-----数据的基本操作 理论+实验——MySQL备份与恢复 MySQL常用数据库函数 MySQL 备份与恢复(完全备份恢复--增量备份恢复+案例演示) MySQL之基础总结部分 Oracle实战优化:递归+分析函数+OLAP函数的应用 Linux中MySQL数据库的使用③-----编码和基本数据类型 理论+实验:MySQL备份与恢复(完整备份、增量备份) “数”聚永川 “智”引未来——永川区大数据智能化产业发展强劲 从一款防疫App感受新加坡大数据智能化气息 “数”聚永川“智”引未来——永川区大数据智能化产业发展强劲 从连接量变到数据质变 物联网将二次爆发 重磅发布!猎芯半导体首创全球最小支持5G物联网的多模多频射频PA芯片 从精准授信到助企惠民,江苏银行物联网金融派上大用场 Python Selenium UI自动化_WebDriver元素_8大定位方式+总结(持续更新完善) Python中的继承、抽象基类和接口 Datawhale学习笔记【阿里云天池 金融风控-贷款违约预测】task1 赛题理解 Pytorch - torchvision计算机视觉工具库 linux 重点笔记 Ubuntu18.04安装ROS Melodic(一路到站型) 小甲鱼笔记:数据结构——线性表(一)线性表的顺序存储结构,线性表顺序存储结构的增,删,插入元素操作 实战比特币脚本编程(1) JAVA WEB DAY 01_Tomcat & Servlet Java基础算法之堆排序(Heap Sort) synchronized批量重偏向与批量撤销 终于等到了!阿里P8历时九个月整理,Java面试宝典,核心知识点笔记在此 “数字心脏”动态解析消费密码,国家级消费市场大数据联合实验室在上海先行先试 全世界运行着大约230亿台物联网设备,安全问题如何解? 物联网产业园&thinkplus解决方案中心国学讲座如期而至 都是程序员,凭什么他能站在鄙视链的顶端? 猛男必看!去小红书做程序员是种什么体验 drozer提示[Errno 2] No such file or directory 【STM32】NB-iOT BC35-G模块 AT指令应用设计指导(附代码) 【北京迅为】i.MX6ULL终结者编译LED汇编程序 Linux系统读写网卡PHY寄存器工具 洛谷:P1226 【模板】快速幂||取余运算(分治,数学) 【2020顶会KDD】AutoST:面向时空预测的高效神经网络学习模型
您的位置:首页 >前端 >

通过RabitMQ实现分布式事务

通过RabitMQ实现分布式事务题

前言业务需求核心原理核心难点解决问题思路代码实现其他总结

前言

这篇文章是通过学习哔哩哔哩中的视频“阿里架构师如何30分钟基于MQ解决分布式事务问题”,原视频连接https://www.bilibili.com/video/BV15p4y1D7d7?p=2 本人水平有限,如有误导,欢迎斧正,一起学习,共同进步!

业务需求

这是一次简单的模拟分布式事务的:假设我们现在需要支付宝向余额宝转账100元。支付宝(系统A)向余额宝(系统B)转账100元,支付宝余额-100,余额宝余额加100。这涉及到两个系统,需要用到分布式事务。

核心原理

系统A(支付宝系统)完成相关业务(此次是扣款操作)后,向MQ中发送消息,同时往系统A(支付宝系统)的消息表中插入一条消息,状态是未确认(为什么要往消息表插入数据后面会有解释);系统B(余额宝系统)从MQ中拿到消息,完成相应的业务(此次是加款操作)后,往系统B(余额宝系统)的消息表插入一条消息,并通知系统A(支付宝系统),当前消息已经被成功操作(我这加款成功啦,你可以放心啦),系统A(支付宝系统)监听到系统B(余额宝系统)发送来的消息后,修改系统A(支付宝系统)本地的消息表的这条消息的状态,改为已确认;利用定时任务去定时的扫描系统A的消息表,若是有状态是未确认的消息,则再次将该消息发送到MQ中,等待消息消费者的消费。

核心难点

1、消息丢失:假设消息生产者(此处是系统A,支付宝系统)向MQ发送消息正常发送;消息消费者(此处是系统B,余额宝系统)正常监听到了MQ中的消息,但是由于某种原因,消息消费者未执行相应的业务逻辑(此处就是余额宝未成功+100,说极端点,比如我余额宝数据库直接宕机了,虽然可能性比较小,但是要考虑到这种情况),但是MQ认为消息消费者已经正常的消费了消息,就把消息删除了。这就是典型的消息丢失问题。 2、重复消费消息(保证消息的幂等性):假设消息生产者(系统A,支付宝系统)向MQ发送消息成功,消息消费者(系统B,余额宝系统)成功监听到了消息并成功消费(就是说余额宝成功+100了)。但是消息消费者向消息生产者发送通知的时候,由于网络原因或其他原因,发送失败(余额宝向支付宝说,我钱增加啦,你放心吧,这个步骤发送失败了。这一步也是通过MQ来实现通信的)。此时,由于系统A迟迟未收到系统B的确认消息,那么系统A的本地消息表的状态就一直是未确认,定时器定时扫描的时候,就会把这条消息继续发送到MQ中,这就会导致支付宝扣了一次款(-100),余额宝增加了两次款(+100,第二次又+100)。是不是做梦都要笑醒了~ 在这里插入图片描述

解决问题思路

1、解决第一个问题,就是在系统A中,增加一个消息表(这个消息表是系统A的),只要我相关业务执行成功了(支付宝-100成功了),那么就往这个消息表增加一条数据(消息表核心字段有ID,状态。这个ID类似于Dubbo的服务追踪的穿透ID,流水ID,是俩系统沟通的凭证,有点类似于token一样的,因为我需要通过这个ID来定位到某一个消息)。只有我监听到系统B给我的反馈了以后(系统B给消息A发送消息的时候带着这个消息的ID,可以定位到这个消息),我才将这个消息的状态改为“已消费”,否则就是默认的未消费。定时器会定时的扫描这个表,将状态是未消费的消息,重新发送到MQ中。 2、解决第二个问题,就是在系统B中,也增加一个消息表(这个消息表是系统B自己的),只要我成功的执行完了我的业务代码(余额宝+100成功)以后,我就在这个消息表增加一个消息。即使某种原因,系统A未收到系统B的确认消息,无非就是把同样的消息在重新发送到MQ中,但是系统B本地有已经消费过的消息存根,系统B监听到消息以后,先去系统B的消息表(消息本地存根)中根据监听到的消息ID查,若是能查到,则证明此条消息已经被消费过了,则不在消费,直接给系统A发送消息,说我成功消费了,你改这个消息的状态吧,别在给我发这条消息了。

代码实现

我个人的github链接(仅供参考): https://github.com/zhengtianliang/distributed_transaction_rabbitmq.

1、系统A(支付宝系统)的核心业务代码

package com.alipay.service.impl;import com.alipay.dao.AccountMapper;import com.alipay.dao.AlipayMessageMapper;import com.alipay.entity.Account;import com.alipay.entity.AlipayMessage;import com.alipay.entity.MessageStatus;import com.alipay.rabbitmq.RabbitMqSender;import com.alipay.service.OrderService;import com.alipay.util.json.JsonUtils;import org.springframework.stereotype.Service;import org.springframework.transaction.TransactionStatus;import org.springframework.transaction.annotation.Transactional;import org.springframework.transaction.support.TransactionCallback;import org.springframework.transaction.support.TransactionTemplate;import javax.annotation.Resource;import java.time.LocalDateTime;import java.util.UUID;/** * @author ZhengTianLiang * @date 2020/9/1222:00 * @desc service */@Servicepublic class OrderServiceImpl implements OrderService {@Resourceprivate AccountMapper accountMapper;@Resourceprivate AlipayMessageMapper alipayMessageMapper;@Resourceprivate TransactionTemplate transactionTemplate;@Resourceprivate RabbitMqSender rabbitMqSender;/** * @author ZhengTianLiang * @date 2020/9/1221:57 * @desc 更新余额操作 系统1(支付宝系统的)的扣款操作 */// @Transactional/**// * 不能使用这个注解的原因是,mq发送消息是比较占用资源的,但是这个又不是必须成功的。也就是说步骤1、2// * 是必须同步的。要么同事成功,要么同事失败。而步骤3是可以失败的。因为我要是没往ma发送成功消息,无非就是消息存根表// * 的状态一直是未确认,那我定时任务会再次发送的。但是步骤1和步骤2是必须要么同时成功,要么同时失败。// * 所以我们要尽量避免使用声明式事务,可以使用编程式事务(因为往mq发送消息比较消耗内存,又不是事务,无须与前俩绑定的)// */@Overridepublic void updateAmount(int amount, String userId) {// 创建一个消息存根对象,用来插入到消息存根表、mq发送消息也用这个对象AlipayMessage message = new AlipayMessage(UUID.randomUUID().toString(), userId, LocalDateTime.now(), MessageStatus.NOCONFIRM);// 使用编程式事务Integer execute = transactionTemplate.execute(new TransactionCallback<Integer>() {@Overridepublic Integer doInTransaction(TransactionStatus transactionStatus) {// 1、account进行扣款操作Account account = new Account(userId, amount, LocalDateTime.now());int i = accountMapper.updateAmountById(account);// 2、第一步执行成功的话,往消息存根表中插入消息if (i == 1) {return alipayMessageMapper.insertMessage(message); // 消息存根表成功插入了数据}return 0; // 未成功插入数据}});if (execute > 0){ // 3、步骤1,2都成功的话,往mq中发送消息rabbitMqSender.sendMessage("","",message);}}/** * @author ZhengTianLiang * @date 2020/9/1310:19 * @desc 支付宝监听到了余额宝发来的消息以后,修改消息存根表的状态,改为“已确认” *//** * 有些童鞋可能要问了,这明明是一个单表操作,为啥要加上@Transactional呢? * 因为如果不加事务的话,整个连接池就会有两个链接,也就是说,在同一个类中,有的是用了@Transactional注解, * 有的类没有用@Transactional注解,那么就会导致,有两个连接池对象,一个是spring维护的,一个是mybatis维护的 * 假设没有用@Transactional注解,又用到了mybatis的update,那么mybatis的源码就会从连接池中获取一个链接对象,而 * 这个链接对象是由mybatis维护、管理的。 * 而如果使用了@Transactional注解(或者编程式事务),即使用到了mybatis的update方法,那么也是从spring中拿的连接池对象, * 这一切都是由spring维护、管理的。 * 总结:下面这个方法,加不加@Transactional注解,执行的效果是一样的,但是加了的话,连接池对象会从2个变成1个, * 降低了整个系统的开销。本来设计的初衷就是为了提高系统的吞吐量,要是有两个连接池对象,与设计初衷不符,所以加了此注解 */@Transactional@Overridepublic void updateMessageStatus(String message) { // 余额宝传过来的消息的状态是confirm已确认的AlipayMessage alipayMessage = JsonUtils.jsonToBean(message, AlipayMessage.class);alipayMessageMapper.updateMessageSatus(alipayMessage);}}

上面这块代码有2点需要注意的,第一点是最好不要使用声明式事务@Transactional,而用编程式事务替代它。因为这三个步骤中(第一步扣款、第二步消息存根表插入数据、第三步往mq中发送消息),前两步是不许一起的,而第三步并非是与前两步绑定一起的(前两步必须同时成功或失败,但是第三步没必要,我即使失败了,没往ma发送成功消息,无非就是消息存根表的状态一直是未确认,那我定时任务会再次发送的。但是步骤1和步骤2是必须要么同时成功,要么同时失败)。第二点则是spring管理的连接池对象和mybatis管理的连接池对象的区别,解释在下面:。

/** * 有些童鞋可能要问了,这明明是一个单表操作,为啥要加上@Transactional呢? * 因为如果不加事务的话,整个连接池就会有两个链接,也就是说,在同一个类中,有的是用了@Transactional注解, * 有的类没有用@Transactional注解,那么就会导致,有两个连接池对象,一个是spring维护的,一个是mybatis维护的 * 假设没有用@Transactional注解,又用到了mybatis的update,那么mybatis的源码就会从连接池中获取一个链接对象,而 * 这个链接对象是由mybatis维护、管理的。 * 而如果使用了@Transactional注解(或者编程式事务),即使用到了mybatis的update方法,那么也是从spring中拿的连接池对象, * 这一切都是由spring维护、管理的。 * 总结:下面这个方法,加不加@Transactional注解,执行的效果是一样的,但是加了的话,连接池对象会从2个变成1个, * 降低了整个系统的开销。本来设计的初衷就是为了提高系统的吞吐量,要是有两个连接池对象,与设计初衷不符,所以加了此注解 */

2、RabbitMQ的相关配置

a.RabbitMqConfig
package com.alipay.rabbitmq;import org.springframework.amqp.core.Binding;import org.springframework.amqp.core.BindingBuilder;import org.springframework.amqp.core.Queue;import org.springframework.amqp.core.TopicExchange;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * @author ZhengTianLiang * @date 2020/9/1222:27 * @desc rabbitmq的配置类 */@Configurationpublic class RabbitMqConfig {/** * rabbitmq中,消息发送者只需要知道交换机的名称就行(还需携带路由键),不需要知道队列的名称 * rabbitmq中,消息消费者只需要知道队列名称就行,不需要知道交换器和路由键的名称 *//** * @author ZhengTianLiang * @date 2020/9/1222:42 * @desc 往rabbitmq的broker里面创建一个队列 */@Bean(name = "message")public Queue getQueue() {return new Queue("zheng.alipay.message");}/** * @author ZhengTianLiang * @date 2020/9/1222:46 * @desc 创建交换器 */@Beanpublic TopicExchange getExchange(){return new TopicExchange("zheng.alipay.exchange");}@BeanBinding bindingExchangeMessage(@Qualifier("message") Queue getQueue,TopicExchange getExchange){return BindingBuilder.bind(getQueue).to(getExchange()).with("zheng.alipay.routkey");}}

-b.RabbitMqSender

package com.alipay.rabbitmq;import com.alipay.entity.AlipayMessage;import com.alipay.util.json.JsonUtils;import lombok.extern.slf4j.Slf4j;import org.springframework.amqp.core.AmqpTemplate;import org.springframework.stereotype.Component;import javax.annotation.Resource;/** * @author ZhengTianLiang * @date 2020/9/1222:29 * @desc 消息发送者 */@Slf4j@Componentpublic class RabbitMqSender {@Resourceprivate AmqpTemplate amqpTemplate;/** * @author ZhengTianLiang * @date 2020/9/1222:29 * @desc 从支付宝系统发送消息到mq中 * @param exchange 交换机名称 * @param routingKey 路由键名称 * @param message 要发送的消息 */public void sendMessage(String exchange, String routingKey, AlipayMessage message) {log.info("支付宝系统往mq中发送了消息:" + message.getMessageId());amqpTemplate.convertAndSend(exchange, routingKey, JsonUtils.toJson(message));}}
c.MessageListener
package com.alipay.rabbitmq.listener;import com.alipay.entity.AlipayMessage;import com.alipay.service.OrderService;import com.alipay.util.json.JsonUtils;import lombok.extern.slf4j.Slf4j;import org.springframework.amqp.rabbit.annotation.RabbitListener;import org.springframework.stereotype.Component;import javax.annotation.Resource;/** * @author ZhengTianLiang * @date 2020/9/1312:08 * @desc 支付宝系统的mq的监听器, */@Slf4j@Componentpublic class MessageListener {@Resourceprivate OrderService orderService;/** * @author ZhengTianLiang * @date 2020/9/1312:09 * @desc 监听余额宝发送的消息,用来改变支付宝自己的消息存根表的消息状态 */@RabbitListener(queues = "zheng.moneypay.message") // 它监听的是余额宝的消息队列public void process(String message){orderService.updateMessageStatus(message); // 更新消息的状态}}

3、系统B(余额宝系统)的核心业务逻辑(是写在mq的监听器里面了)

package com.rabbitmq.listener;import com.entity.AlipayMessage;import com.entity.MessageStatus;import com.entity.MoneypayMessage;import com.rabbitmq.RabbitMqSender;import com.service.OrderService;import com.util.json.JsonUtils;import com.util.mapper.MapperUtils;import lombok.extern.slf4j.Slf4j;import org.springframework.amqp.rabbit.annotation.RabbitListener;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;import org.springframework.transaction.TransactionStatus;import org.springframework.transaction.support.TransactionCallback;import org.springframework.transaction.support.TransactionTemplate;import javax.annotation.Resource;/** * @author ZhengTianLiang * @date 2020/9/1311:08 * @desc mq的监听器, 也是核心的业务代码 */@Slf4j@Componentpublic class MessageListener {@Resourceprivate OrderService orderService;@Resourceprivate TransactionTemplate transactionTemplate;@Resourceprivate RabbitMqSender rabbitMqSender;@Value("${moneypay.exchange}")private String exchange;@Value("${moneypay.routkey}")private String routkey;/** * @author ZhengTianLiang * @date 2020/9/1311:40 * @desc 监听消息发送者(支付宝系统)发送来的消息,并作出相应的业务操作 */@RabbitListener(queues = "zheng.alipay.message") // 消息发送者的队列,就是支付宝发送到的队列的队列名public void process(String jsonMessage) {AlipayMessage alipayMessage = JsonUtils.jsonToBean(jsonMessage, AlipayMessage.class);MoneypayMessage moneypayMessage = MapperUtils.mapperBean(alipayMessage, MoneypayMessage.class);// 1、去余额宝自己的消息存根中查,看看能不能根据支付宝系统传过来的消息id来查到数据,能查到则说明消费过了;查不到则是未消费过Integer count = orderService.queryMessageCountById(alipayMessage.getMessageId());Boolean exec = transactionTemplate.execute(new TransactionCallback<Boolean>() {@Overridepublic Boolean doInTransaction(TransactionStatus transactionStatus) {if (count == 0) { //此消息未消费过orderService.updateAmount(moneypayMessage.getAmount(), moneypayMessage.getUserId());// 加款操作orderService.insertMessage(moneypayMessage); // 往余额宝自己的消息本地存根中插入数据}return true;}});if (count > 0) { // 此消息已经被消费过了,则直接通过mq去通知支付宝系统,说这个消息消费过了log.info("此消息已经被消费过了,不做任何操作");}if (exec){ // 去mq中發消息,通知支付宝,说此消息已经被消费过了alipayMessage.setStatus(MessageStatus.CONFIRM);rabbitMqSender.sendMessage(exchange,routkey,alipayMessage);}}}

4、mq的相关配置

a.RabbitMqConfig
package com.rabbitmq;import org.springframework.amqp.core.Binding;import org.springframework.amqp.core.BindingBuilder;import org.springframework.amqp.core.Queue;import org.springframework.amqp.core.TopicExchange;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * @author ZhengTianLiang * @date 2020/9/1222:27 * @desc rabbitmq的配置类 */@Configurationpublic class RabbitMqConfig {/** * rabbitmq中,消息发送者只需要知道交换机的名称就行(还需携带路由键),不需要知道队列的名称 * rabbitmq中,消息消费者只需要知道队列名称就行,不需要知道交换器和路由键的名称 *//** * @author ZhengTianLiang * @date 2020/9/1222:42 * @desc 往rabbitmq的broker里面创建一个队列 */@Bean(name = "message")public Queue getQueue() {return new Queue("zheng.moneypay.message");}/** * @author ZhengTianLiang * @date 2020/9/1222:46 * @desc 创建交换器 */@Beanpublic TopicExchange getExchange(){return new TopicExchange("zheng.moneypay.exchange");}@BeanBinding bindingExchangeMessage(@Qualifier("message") Queue getQueue,TopicExchange getExchange){return BindingBuilder.bind(getQueue).to(getExchange()).with("zheng.moneypay.routkey");}}

-b.RabbitMqSender

package com.rabbitmq;import com.util.json.JsonUtils;import lombok.extern.slf4j.Slf4j;import org.springframework.amqp.core.AmqpTemplate;import org.springframework.stereotype.Component;import javax.annotation.Resource;/** * @author ZhengTianLiang * @date 2020/9/1222:29 * @desc 消息发送者 */@Slf4j@Componentpublic class RabbitMqSender {@Resourceprivate AmqpTemplate amqpTemplate;/** * @param exchange 交换机名称 * @param routingKey 路由键名称 * @param message要发送的消息 * @author ZhengTianLiang * @date 2020/9/1222:29 * @desc 从支付宝系统发送消息到mq中 */public void sendMessage(String exchange, String routingKey, Object message) {log.info("支付宝系统往mq中发送了消息:" + message);amqpTemplate.convertAndSend(exchange, routingKey, JsonUtils.toJson(message));}}

5、余额宝系统的service

package com.service.impl;import com.dao.AccountMapper;import com.dao.MoneypayMessageMapper;import com.entity.Account;import com.entity.MoneypayMessage;import com.service.OrderService;import org.springframework.stereotype.Service;import javax.annotation.Resource;import java.time.LocalDateTime;import java.util.UUID;/** * @author ZhengTianLiang * @date 2020/9/1311:27 * @desc service */@Servicepublic class OrderServiceImpl implements OrderService {@ResourceMoneypayMessageMapper messageMapper;@ResourceAccountMapper accountMapper;/** * @author ZhengTianLiang * @date 2020/9/1311:31 * @desc 这个是账户的新增操作 */@Overridepublic void updateAmount(Integer amount, String userId) {Account account = new Account(userId,amount, LocalDateTime.now());accountMapper.updateAmountById(account);}/** * @author ZhengTianLiang * @date 2020/9/1311:27 * @desc 根据消息id查到消息的数量(用来判断该消息是否被消费过) */@Overridepublic Integer queryMessageCountById(String messageId) {return messageMapper.queryCountByMessageId(messageId);}/** * @author ZhengTianLiang * @date 2020/9/1311:27 * @desc 往余额宝系统的消息存根表中插入数据 */@Overridepublic void insertMessage(MoneypayMessage message) {messageMapper.insertMessage(message);}}

其他

下面是我之前学习的时候记录的一些笔记,希望能对读者提供一些帮助。
二、分布式事务的三种比较常见的解决方案:1、基于XA协议的两阶段提交①XA规范中分布式事务由AP,RM,TM三部分组成。具体点就是,应用程序(AP),事务管理器(TM),资源管理器(RM),通信资源管理器(CRM)四个部分。一般,常见的事务管理器(TM)是交易中间件,常见的资源管理器(RM)是数据库,常见的通信资源管理器(CRM)是消息中间件AP:定义事务边界(定义事务开始和结束),并访问事务边界内的资源RM:资源管理器,管理计算机共享的资源,许多软件都可以去访问这些资源,资源包含比如数据库,文件系统,打印机服务器等TM:负责管理全局事务,分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚、失败恢复等②两阶段协议:第一阶段:TM要求所有的RM准备提交对应的事务分支,询问RM是否有能力保证成功的提交事务分支,RM根据自己的情况,如果判断自己进行的工作可以被提交,那就对工作的内容就行持久化,并给TM回执OK,否则给TM回执NO,RM在发送了否定答复并回滚了已经的工作后,就可以丢弃这个事务分支信息了。第二阶段:TM根据阶段1各个RM prepare的结果,决定是提交还是回滚事务,如果所有的RM都prepare成功,那么TM通知所有的RM进行提交,如果有RM prepare回执NO的话,则TM通知所有的RM回滚自己的事务分支③XA协议两阶段提交的优缺点:优点:进来保持了数据的强一致性,适合对数据强一致性要求跟高的领域(并非100%保证强一致性)缺点:实现复杂,牺牲了可用性,对性能影响比较大,不适合高并发高性能的场景2、TCC补偿机制TCC其实就是采用的补偿机制,其核心思想是,针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作,他分为三个阶段:·Try阶段,主要是对业务系统做检测已经资源御灵·Confirm阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行Confirm阶段时,默认Confirm阶段是不会出错的。即:主要Try成功,Confirm一定成功·Cancel阶段主要是业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放②TCC补偿机制的优缺点:优点:相比于两阶段提交,可用性比较强(因为两阶段涉及到了锁的概念)缺点:数据的一致性要差一些,TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码。在一些场景中,一些业务流程可能用TCC不太好定义处理3、消息最终一致性消息最终一致性应该是业界使用最多的,其核心思想是将分布式事务拆分为本地事务进行处理。①基本流程:假设有两个事务,第一个事务先写入业务数据,然后在写入消息数据(此时会额外建立一个消息表来记录我要发送的消息内容),写完消息数据之后,在将数据发送给MQ(第一阶段到此结束)。第二个事务要用到这个数据第二个事务就从MQ中拿到对应的数据,拿到这个消息,在写入业务数据。如果说事务成功了,就修改消息表(就是事务1新建的,用来存储要发送的消息内容的消息表)中的状态。如果说事务操作这个消息失败了,也要给事务1说一下,然后事务1在调用一些补偿的代码来执行一些数据库回滚的操作。若写入业务事务成功,(事务1)发送到MQ失败了,会怎么样?则消息表会存积大量的未处理的消息数据,此时,会有一个另外的线程去定时的去扫描这个消息表,若发现有大量的未处理的消息,则在进行一些对应的补偿逻辑、②消息最终一致性的优缺点:优点:一种非常经典的实现,避免了分布式事务,实现了最终一致性缺点:消息表会耦合到业务系统中,若没有封装好的解决方案,会有很多的杂活。

总结

其实分布式的事务原理肯定都一样:利用一个总的事务管理器来管理一个个的零散的本地事务,假如全部成功,则成功;有任意一个失败,则回滚。所以分布式事务几乎都是非常影响系统性能的。 现在比较常用的有TX-LCN、seata等。还有之前的基于XA两阶段提交、TCC补偿机制等等。

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。