通过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)。是不是做梦都要笑醒了~
通过RabitMQ实现分布式事务_第1张图片

解决问题思路

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/12  22:00
 * @desc service
 */

@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private AccountMapper accountMapper;

    @Resource
    private AlipayMessageMapper alipayMessageMapper;

    @Resource
    private TransactionTemplate transactionTemplate;

    @Resource
    private RabbitMqSender rabbitMqSender;

    /**
     * @author ZhengTianLiang
     * @date 2020/9/12  21:57
     * @desc 更新余额操作         系统1(支付宝系统的)的扣款操作
     */
    // @Transactional    /**
    //     * 不能使用这个注解的原因是,mq发送消息是比较占用资源的,但是这个又不是必须成功的。也就是说步骤1、2
    //     * 是必须同步的。要么同事成功,要么同事失败。而步骤3是可以失败的。因为我要是没往ma发送成功消息,无非就是消息存根表
    //     * 的状态一直是未确认,那我定时任务会再次发送的。但是步骤1和步骤2是必须要么同时成功,要么同时失败。
    //     * 所以我们要尽量避免使用声明式事务,可以使用编程式事务(因为往mq发送消息比较消耗内存,又不是事务,无须与前俩绑定的)
    //     */
    @Override
    public 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() {
            @Override
            public 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/13  10:19
     * @desc 支付宝监听到了余额宝发来的消息以后,修改消息存根表的状态,改为“已确认”
     */
    /**
     * 有些童鞋可能要问了,这明明是一个单表操作,为啥要加上@Transactional呢?
     * 因为如果不加事务的话,整个连接池就会有两个链接,也就是说,在同一个类中,有的是用了@Transactional注解,
     * 有的类没有用@Transactional注解,那么就会导致,有两个连接池对象,一个是spring维护的,一个是mybatis维护的
     * 假设没有用@Transactional注解,又用到了mybatis的update,那么mybatis的源码就会从连接池中获取一个链接对象,而
     * 这个链接对象是由mybatis维护、管理的。
     * 而如果使用了@Transactional注解(或者编程式事务),即使用到了mybatis的update方法,那么也是从spring中拿的连接池对象,
     * 这一切都是由spring维护、管理的。
     * 总结:下面这个方法,加不加@Transactional注解,执行的效果是一样的,但是加了的话,连接池对象会从2个变成1个,
     * 降低了整个系统的开销。本来设计的初衷就是为了提高系统的吞吐量,要是有两个连接池对象,与设计初衷不符,所以加了此注解
     */
    @Transactional
    @Override
    public 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/12  22:27
 * @desc rabbitmq的配置类
 */

@Configuration
public class RabbitMqConfig {

    /**
     * rabbitmq中,消息发送者只需要知道交换机的名称就行(还需携带路由键),不需要知道队列的名称
     * rabbitmq中,消息消费者只需要知道队列名称就行,不需要知道交换器和路由键的名称
     */

    /**
     * @author ZhengTianLiang
     * @date 2020/9/12  22:42
     * @desc 往rabbitmq的broker里面创建一个队列
     */
    @Bean(name = "message")
    public Queue getQueue() {
        return new Queue("zheng.alipay.message");
    }

    /**
     * @author ZhengTianLiang
     * @date 2020/9/12  22:46
     * @desc 创建交换器
     */
    @Bean
    public TopicExchange getExchange(){
        return new TopicExchange("zheng.alipay.exchange");
    }

    @Bean
    Binding 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/12  22:29
 * @desc 消息发送者
 */

@Slf4j
@Component
public class RabbitMqSender {

    @Resource
    private AmqpTemplate amqpTemplate;

    /**
     * @author ZhengTianLiang
     * @date 2020/9/12  22: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/13  12:08
 * @desc 支付宝系统的mq的监听器,
 */

@Slf4j
@Component
public class MessageListener {
     

    @Resource
    private OrderService orderService;

    /**
     * @author ZhengTianLiang
     * @date 2020/9/13  12: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/13  11:08
 * @desc mq的监听器, 也是核心的业务代码
 */

@Slf4j
@Component
public class MessageListener {
     

    @Resource
    private OrderService orderService;

    @Resource
    private TransactionTemplate transactionTemplate;

    @Resource
    private RabbitMqSender rabbitMqSender;

    @Value("${moneypay.exchange}")
    private String exchange;

    @Value("${moneypay.routkey}")
    private String routkey;


    /**
     * @author ZhengTianLiang
     * @date 2020/9/13  11: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>() {
     
            @Override
            public 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/12  22:27
 * @desc rabbitmq的配置类
 */

@Configuration
public class RabbitMqConfig {
     

    /**
     * rabbitmq中,消息发送者只需要知道交换机的名称就行(还需携带路由键),不需要知道队列的名称
     * rabbitmq中,消息消费者只需要知道队列名称就行,不需要知道交换器和路由键的名称
     */

    /**
     * @author ZhengTianLiang
     * @date 2020/9/12  22:42
     * @desc 往rabbitmq的broker里面创建一个队列
     */
    @Bean(name = "message")
    public Queue getQueue() {
     
        return new Queue("zheng.moneypay.message");
    }

    /**
     * @author ZhengTianLiang
     * @date 2020/9/12  22:46
     * @desc 创建交换器
     */
    @Bean
    public TopicExchange getExchange(){
     
        return new TopicExchange("zheng.moneypay.exchange");
    }

    @Bean
    Binding 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/12  22:29
 * @desc 消息发送者
 */

@Slf4j
@Component
public class RabbitMqSender {
     

    @Resource
    private AmqpTemplate amqpTemplate;

    /**
     * @param exchange   交换机名称
     * @param routingKey 路由键名称
     * @param message    要发送的消息
     * @author ZhengTianLiang
     * @date 2020/9/12  22: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/13  11:27
 * @desc service
 */

@Service
public class OrderServiceImpl implements OrderService {
     

    @Resource
    MoneypayMessageMapper messageMapper;

    @Resource
    AccountMapper accountMapper;


    /**
     * @author ZhengTianLiang
     * @date 2020/9/13  11:31
     * @desc 这个是账户的新增操作
     */
    @Override
    public void updateAmount(Integer amount, String userId) {
     
        Account account = new Account(userId,amount, LocalDateTime.now());
        accountMapper.updateAmountById(account);
    }

    /**
     * @author ZhengTianLiang
     * @date 2020/9/13  11:27
     * @desc 根据消息id查到消息的数量(用来判断该消息是否被消费过)
     */
    @Override
    public Integer queryMessageCountById(String messageId) {
     
        return messageMapper.queryCountByMessageId(messageId);
    }

    /**
     * @author ZhengTianLiang
     * @date 2020/9/13  11:27
     * @desc 往余额宝系统的消息存根表中插入数据
     */
    @Override
    public 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补偿机制等等。

你可能感兴趣的:(分布式事务,中间件,分布式,java)