借助消息队列解决分布式事务

先介绍一下RabbitMQ的基本概念

核心概念

Queue:真正存储数据的地方
Exchange接受请求,转存数据
Bind:收到请求后存储到哪里
消息生产者:发送数据的应用
消息消费者:取出数据处理的应用

Bind的几种分发规则:Direct、Topic、Fanout
Fanout:与该Exchange绑定的Queue都发一份数据
Direct:给完全匹配的Queue发送数据,routingkey与bindkey完全一致
Topic:给模糊匹配的Queue发送数据,binding key中可以存在两种特殊字符“”与“#”,用于做模糊匹配,其中“”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)

工作流程

生产者将数据通过RabbitMQ-client发送到RabbitMQ-server中的exchange,exchange根据路由配置,分发给Queue,消费者从Queue拿到数据
借助消息队列解决分布式事务_第1张图片

分布式事务的产生

多个系统相互配合工作,产生数据一致性问题。
例如外卖场景中,下单中心,运单中心两个系统要配合工作,必须保证两个系统数据一致性。
错误的解决方案:
使用API接口调用,下单中心插入数据,调用运单中心的API接口处理数据,并启动事务回滚。
咋一看这场景没有什么问题,毕竟有事务回滚,一起成功一起失败,但其实存在API调用超时的情况,此时下单中心以为调用失败回滚,而运单中心只是超时仍会继续执行程序,从而造成两个系统数据不一致。
假设API调用成功,也有可能是在订单中心提交事务时失败了,此时订单中心回滚,而API已经调用,下单中心的数据已经产生,数据不一致。

使用消息队列解决分布式事务

借助消息队列解决分布式事务_第2张图片
问题的核心就是保证可靠生产与可靠消费
可靠生产下单中心处理数据和状态表更改应该保证事务一致。生产者往消息队列发送数据时,在本地建立一张状态表,看是否成功发送给队列。利用RabbitMQ的确认机制看是否重发还是定时扫描状态表重发,保证可靠生产。兜底方案还是定时扫描状态表。
代码

#开启mq的消息回执确认机制
spring.rabbitmq.publisher-confirms=true
@PostConstruct   //回调函数
    public void setUp(){
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                //ack为true 代表发送成功
                if(!ack){   //发送失败
                    System.out.println(cause);
                    return;
                }
                System.out.println("发送成功");
                //真实应该在本地记录表中更改为已发送标志

            }
        });
    }

可靠消费:消费端开启手动ACK,给MQ队列发送确认信息。对每条数据进行记录,一旦有异常记录可以试着去重试重新要求MQ再发数据,但不能重试太多次。可以将数据主键插入数据库,这样同一数据就不会执行两次,或者使用redis记录数据操作。
代码

#开启消费端手动确认机制
spring.rabbitmq.listener.simple.acknowledge-mode=manual
@RabbitListener(queues = "hello")
    public void process(String message, Channel channel , @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
        try {
            System.out.println("Receiver1  : " + message);
            //保证幂等性 防止重复数据重复处理

            //回复MQ 已经接受到数据一切正常
            channel.basicAck(tag,false);
        } catch (IOException e) {
            //出现异常,通知MQ重发数据,但要记录下数据的异常处理次数
            //对于错误数据另外处理,人工干预
            channel.basicNack(tag,false,false);
        }
    }

优缺点

  • 通用性强
  • 扩展性强

缺点

  • 适合异步场景
  • 处理有延迟,业务上需要适应

你可能感兴趣的:(架构)