RocketMQ消息事务

Half(Prepare) Message 半消息(预处理消息)

半消息是一种特殊的消息类型,该状态的消息暂时不能被Consumer消费。当一条事务消息被成功投递到Broker上,但是Broker并没有接收到Producer发出的二次确认时,该事务消息就处于"暂时不可被消费"状态,该状态的事务消息被称为半消息。

Message Status Check 消息状态回查

由于网络抖动、Producer重启等原因,可能导致Producer向Broker发送的二次确认消息没有成功送达。如果Broker检测到某条事务消息长时间处于半消息状态,则会主动向Producer端发起回查操作,查询该事务消息在Producer端的事务状态(Commit 或 Rollback)。可以看出,Message Status Check主要用来解决分布式事务中的超时问题。

执行流程

在这里插入图片描述

  • Step1:Producer向Broker端发送Half Message;
  • Step2:Broker ACK,Half Message发送成功;
  • Step3:Producer执行本地事务;
  • Step4:本地事务完毕,根据事务的状态,Producer向Broker发送二次确认消息,确认该HalfMessage的Commit或者Rollback状态。Broker收到二次确认消息后,对于Commit状态,则直接发送到Consumer端执行消费逻辑,而对于Rollback则直接标记为失败,一段时间后清除,并不会发给Consumer。正常情况下,到此分布式事务已经完成,剩下要处理的就是超时问题,即一段时间后Broker仍没有收到Producer的二次确认消息;
  • Step5:针对超时状态,Broker主动向Producer发起消息回查;
  • Step6:Producer处理回查消息,返回对应的本地事务的执行结果;
  • Step7:Broker针对回查消息的结果,执行Commit或Rollback操作,同Step4。

举个栗子

生产者

package transaction;

import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.producer.*;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;

public class Producer {
    public static void main(String[] args) throws Exception{
        //创建消息生产者
        TransactionMQProducer producer = new TransactionMQProducer("demo_producer_transaction_group");
        //2、生产者要主动联系namesrvAddr
        producer.setNamesrvAddr("8.131.84.120:9876");
        //指定消息监听对象,用于执行本地事务,消息回查
        producer.setTransactionListener(new TransactionListener() {
            //执行本地事务
            @Override
            public LocalTransactionState executeLocalTransaction(Message message, Object o) {
                if (StringUtils.equals("TAGA",message.getTags())){
                    return LocalTransactionState.COMMIT_MESSAGE;
                }else if (StringUtils.equals("TAGB",message.getTags())){
                    return  LocalTransactionState.ROLLBACK_MESSAGE;
                }else if (StringUtils.equals("TAGC",message.getTags())){
                    //如果没有收到事务消息的处理就会调用回查方法
                    return LocalTransactionState.UNKNOW;
                }
                return LocalTransactionState.UNKNOW;
            }
            //消息回查
            @Override
            public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
                System.out.println(messageExt.getTags());
                return LocalTransactionState.COMMIT_MESSAGE;
            }
        });
        //3、连接成功后要启动生产者
        producer.start();

        String tags[] = {"TAGA","TAGB","TAGC"};
        for (int i = 0; i < 3; i++) {
            //4、创建消息类,包含topic和body
            Message message = new Message(
                    "Topic_transaction_demo", //主题
                    tags[i],                        //消息过滤
                    "keys_",                   //消息的唯一值
                    ("hello world").getBytes(RemotingHelper.DEFAULT_CHARSET)
            );
            //5、生产者发送事务消息
            TransactionSendResult result = producer.sendMessageInTransaction(message, null);
            System.out.println(result);
        }
        //6、关闭生产者
        //producer.shutdown();
    }
}

消费者

package transaction;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.remoting.common.RemotingHelper;

import java.io.UnsupportedEncodingException;
import java.util.List;

public class Consumer {
    public static void main(String[] args) throws Exception {
        //1、创建DefaultMQPushConsumer
        DefaultMQPushConsumer mqPushConsumer = new DefaultMQPushConsumer("demo-consumer-group");
        //2、设置namesrv地址
        mqPushConsumer.setNamesrvAddr("8.131.84.120:9876");
        //3、设置subscribe读取主题信息
        /**
         * 生产者类似一个作者,namesrv类似杂志社,消费者必须先订阅某家报社,才可以收到生产者给报社写的文章
         * 每个消费者只能订阅一个topic
         * topic:关注消息的地址
         * 过滤器 * :表示不过滤
         */
        mqPushConsumer.subscribe("Topic_transaction_demo","*");
        //4、消费者注册个监听器,这样namesrv里传进来生产者提供的消息后,就可以及时知道了
        //MessageListenerConcurrently 是普通消息接收,MessageListenerOrderly 是顺序消息接收
        mqPushConsumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext consumeOrderlyContext) {
                for (MessageExt msg : msgs) {
                    try {
                        //获取主题
                        String topic = msg.getTopic();
                        //获取标签
                        String tags = msg.getTags();
                        //获取消息
                        String result = new String(msg.getBody(), RemotingHelper.DEFAULT_CHARSET);
                        System.out.println("subject: "+topic+", tag: "+tags+", message: "+result);
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                        //消息重试
                        return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
                    }
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        } );
        //开启Consumer
        mqPushConsumer.start();
        System.out.println("consumer start...");
    }
}

上述解释:

消费者最终消费了两个消息,当本地事务状态是 UNKNOW 时,就调用消息回查方法checkLocalTransaction

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值