RocketMq(六)-------RocketMq之事务消费

本文详细介绍了RocketMQ中事务消息的工作原理及其实现方式。包括如何通过Producer发送事务消息、如何处理本地事务以及如何实现消息的最终一致性。此外,还提供了具体的代码示例帮助理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

一:背景

对于消息队列,必定绕不开的几个问题是:消息的可靠性,消息的重复,消息的顺序,消息的事务性。前面已经讲了消息的顺序性问题,接下来要了解的就是消息的事务性问题了。好,接下来就来了解一下消息的事务。

二:实例

producer(生产者):

public class Producer {

    public static void main(String[] args) throws MQClientException, InterruptedException {
        String group_name = "transaction_producer";

        final TransactionMQProducer producer = new TransactionMQProducer(group_name);

        producer.setNamesrvAddr("192.168.0.2:9876;192.168.0.3:9876");
        producer.setCheckRequestHoldMax(200);
        producer.setCheckThreadPoolMaxSize(20);
        producer.setCheckThreadPoolMinSize(5);
        producer.start();
        //服务器回调producer,检查本地事务分支成功还是失败
        producer.setTransactionCheckListener(new TransactionCheckListener() {

            @Override
            public LocalTransactionState checkLocalTransactionState(MessageExt msg) {
                System.out.println("state --" + new String(msg.getBody()));
                return LocalTransactionState.COMMIT_MESSAGE;
            }
        });

        TransactionExecuterImpl tranExecuter = new TransactionExecuterImpl();

        for(int i=0; i<2 ; i++) {
            try {
                Message msg = new Message("TopicTransaction","transaction" + i,"key",("hello " + i).getBytes());
                SendResult sendResult = producer.sendMessageInTransaction(msg, tranExecuter, "tq");
                System.out.println(sendResult);
            }catch(Exception e) {
                e.printStackTrace();
            }
        }
        Thread.sleep(3000);
        producer.shutdown();
    }

}

 TransactionExecuterImpl:

/*
 * 执行本地事务,由客户端回调
 */
public class TransactionExecuterImpl implements LocalTransactionExecuter {

    @Override
    public LocalTransactionState executeLocalTransactionBranch(Message msg, Object arg) {
        System.out.println("msg :" + new String(msg.getBody()));
        System.out.println("arg :" + arg);
        String tag = msg.getTags();
        if(tag.equals("transaction1")) {
            //这里有一个分阶段提交任务概念
            System.out.println("这里处理业务逻辑,如操作数据库,失败情况下进行ROLLBACK");
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }

        return LocalTransactionState.COMMIT_MESSAGE;
        //return LocalTransactionState.UNKNOW;
    }

}

 

  consumer(消费者):

public class Consumer {

    public static void main(String[] args) throws MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("transaction_consumer");
        //设置consumer第一次启动是从队列头部开始还是尾部开始消费,若非第一次启动,那么按照上次消费的位置继续消费
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("TopicTransaction", "*");
        //批量消费,一次消费多少条消息,默认为1条,最大情况能拿多少条不代表每次能拿这么多条
        //consumer.setConsumeMessageBatchMaxSize(3);
        //messageListenerOrderly 保证顺序消费,消费端接收的是同一个队列的消息,避免多线程时顺序错乱
        /*consumer.registerMessageListener(new MessageListenerOrderly() {

            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> arg0, ConsumeOrderlyContext arg1) {

                return null;
            }
        });*/
        consumer.setConsumeThreadMax(10);
        consumer.setConsumeThreadMin(10);
        consumer.registerMessageListener(new MessageListenerConcurrently() {

            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                //System.out.println(Thread.currentThread().getName() + "Receive: " + msgs);
                //获取一次性消费多少条消息
                //System.out.println("消息条数 : " + msgs.size());
                MessageExt msg1 = null;
                try {
                    for(MessageExt msg : msgs) {
                        msg1 = msg;
                        String topic = msg.getTopic();
                        String msgbody = new String(msg.getBody(),"utf-8");
                        String tag = msg.getTags();
                        System.out.println("收到消息: " + "topic:" + topic + " tags:" + tag + " msg:" + msgbody);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    //若已经重试了5次则不再重试
                    if(msg1.getReconsumeTimes() == 5) {
                        //此处记录日志操作。。。
                        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                    }
                    return ConsumeConcurrentlyStatus.RECONSUME_LATER;
                }

                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.setNamesrvAddr("192.168.0.2:9876;192.168.0.3:9876");
        consumer.start();
        System.out.println("Consumer started...");
    }

}

 结果输出:

producer console:

msg :hello 0
arg :tq
SendResult [sendStatus=SEND_OK, msgId=C0A8000200002A9F0000000000003B1C, messageQueue=MessageQueue [topic=TopicTransaction, brokerName=broker-a, queueId=0], queueOffset=0]
msg :hello 1
arg :tq
这里处理业务逻辑,如操作数据库,失败情况下进行ROLLBACK
SendResult [sendStatus=SEND_OK, msgId=C0A8000200002A9F0000000000003C9E, messageQueue=MessageQueue [topic=TopicTransaction, brokerName=broker-a, queueId=1], queueOffset=0]

consume console:

收到消息: topic:TopicTransaction tags:transaction0 msg:hello 0

 

三:原理

1.0、 Producer发送消息到RocketMQ,这条消息我们暂且称之为message1 并且是transaction状态的事务消息。

1.1、MQ把message1存入数据库,并且是状态是prepared。

1.2、RocketMQ回调Producer中的本地事务。(本地事务由三个状态COMMIT_MESSAGE、ROLLBACK_MESSAGE、UNKNOW)。

1.2.1 、本地事务处理完成后,无论成功还是失败都会有一个状态,如果成功的话,Producer就会发送COMMIT_MESSAGE状态表示确认消息到RocketMQ上。

1.2.2、然后把message1这个消息存储到consumer queue中,并在数据库中把这条prepared的消息标记为commited。

1.2.3、这条消息就被Consumer消费了。

1.3.1、如果Producer的事务处理返回了一个UNKNOW状态。因为broker会定时的去扫描数据库,如果数据库中的数据状态是commited的,那么就清除这条数据。

1.4、如果数据库中数据的状态还是prepared的。那MQ就会主动的去调用Producer中的check方法。

1.5.1、check方法再去查本地的数据库看有没有减钱,如果没减钱的话就rollback,

1.6.1、rollback后Producer又发了一条ROLLBACK_MESSAGE给MQ。

1.7.1、MQ收到这条消息后,就会把MQ的数据库中对应的prepared数据给清除掉。那么这条数据也就不会被Consumer端消费了

1.5.2、check方法查本地数据库看有没有减钱,如果减钱了。

1.6.2、会给MQ发送一个COMMIT_MESSAGE。

1.7.2、MQ还会去查自己的数据库,然后把数据库中对应的数据给清除掉

 四:致谢

  本文主要摘抄自以下文章:

 RocketMQ-事务消费

 RocketMq——顺序消费和事务

分布式开放消息系统(RocketMQ)的原理与实践

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值