一、主要场景

事物消费需要先说说什么是分布式事务。比如经典的跨行转账:从工商银行转到建设银行,也就是我从工商银行扣除1000元之后,我的建设银行也必须加1000元。这样才能保证数据的一致性。假如工商银行转1000元之后,建设银行的服务器突然宕机,那么我扣除了1000,但是并没有在建设银行给我加1000,就出现了数据的不一致。因此加1000和减1000才行,减1000和减1000必须一起成功,一起失败。

rocketmq的事务消费可以用来解决分布式事务问题,但目前刚接触到rocketmq,先只贴一下事务消费的简单实现,真正的分布式事务极其复杂,等后续有机会再研究。

二、实现过程

首先准备两个类,分别实现TransactionCheckListener接口和LocalTransactionExecuter接口

TransactionCheckListenerImpl类,实现了TransactionCheckListener接口

package com.huaguoguo.example.rocketmq.transaction;

import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionCheckListener;
import org.apache.rocketmq.common.message.MessageExt;

/**
  * @Author:huaguoguo
  * @Description: 服务器回查客户端,
 *                  由于开源版本的rocketMQ3.0.6之后的版本阉割了事务会回查机制,所以这里的方法不会被执行
 *                  但是不加这个类又会抛异常o(* ̄︶ ̄*)o
 * @Date: 2018/5/17 19:17
  */
public class TransactionCheckListenerImpl implements TransactionCheckListener{

    //在这里,我们可以根据由MQ回传的key去数据库查询,这条数据到底是成功了还是失败了。
    @Override
    public LocalTransactionState checkLocalTransactionState(MessageExt msg) {
        System.out.println("未决事务,服务器回查客户端msg =" + new String(msg.getBody().toString()));
        // return LocalTransactionState.ROLLBACK_MESSAGE;

        return LocalTransactionState.COMMIT_MESSAGE;

        // return LocalTransactionState.UNKNOW;
    }
}

TransactionExecuterImpl,实现了LocalTransactionExecuter接口

package com.huaguoguo.example.rocketmq.transaction;

import org.apache.rocketmq.client.producer.LocalTransactionExecuter;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.common.message.Message;

/**
  * @Author:huaguoguo 
  * @Description: 执行本地事务
 * @Date: 2018/5/18 11:39
  */ 
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 tags = msg.getTags();

        if (tags.equals("transaction2")) {
            System.out.println("======A账号余额减1000============,失败了  -进行ROLLBACK");
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
        System.out.println("======A账号余额减1000============,成功了  -发送确认消息");
        return LocalTransactionState.COMMIT_MESSAGE;
    }
}

生产者类,这里用到TransactionMQProducer

package com.huaguoguo.example.rocketmq.transaction;

import org.apache.rocketmq.client.exception.MQClientException;
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 MQClientException, InterruptedException {

        TransactionMQProducer producer = new TransactionMQProducer("transaction_Producer");
        producer.setNamesrvAddr("xxx:9876");


        // 事务回查最小并发数
        producer.setCheckThreadPoolMinSize(2);
        // 事务回查最大并发数
        producer.setCheckThreadPoolMaxSize(2);
        // 队列数
        producer.setCheckRequestHoldMax(2000);
        /*
        * 服务器回调producer,检查本地事务分支成功还是失败
        * */
        producer.setTransactionCheckListener(new TransactionCheckListenerImpl());
        producer.start();

        for (int i = 1; i < 3; i++) {
            try {

                /*
                 * 创建一个消息对象
                 */
                Message msg = new Message("TopicTransactionTest" /* Topic */,
                        "transaction" + i /* Tag */,
                        "KEY" + i /* key */,
                        ("转账消息"+i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
                );

                /*
                 * 调用producer发送消息来将消息传递给brokers。
                 */
                SendResult sendResult = producer.sendMessageInTransaction(msg,new TransactionExecuterImpl(),null);
                System.out.printf("%s%n", sendResult);
            } catch (Exception e) {
                e.printStackTrace();
                Thread.sleep(1000);
            }
        }

        /**
         * 应用退出时,要调用shutdown来清理资源,关闭网络连接,从MetaQ服务器上注销自己
         * 注意:我们建议应用在JBOSS、Tomcat等容器的退出钩子里调用shutdown方法
         */
        producer.shutdown();
    }
}

消费者类

package com.huaguoguo.example.rocketmq.transaction;

import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class Consumer {

    public static void main(String[] args) throws InterruptedException, MQClientException {

        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("transaction_Consumer");


        consumer.setNamesrvAddr("xxx:9876");
        /*
        * 每次拉取10条
        * */
        consumer.setConsumeMessageBatchMaxSize(10);
        /*
         * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
         * 如果非第一次启动,那么按照上次消费的位置继续消费
         */
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);


        /**
         * 订阅指定topic下所有消息
         * 注意:一个consumer对象可以订阅多个topic
         */
        consumer.subscribe("TopicTransactionTest", "*");

        /*
         *  注册回调,以便在从brokers那里获得的消息到达时执行。
         */
        consumer.registerMessageListener(new MessageListenerConcurrently() {

            /**
             * 这个方法是消息到达时执行消费逻辑
             * 默认msgs里只有一条消息,可以通过设置consumeMessageBatchMaxSize参数来批量接收消息
             */
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                            ConsumeConcurrentlyContext context) {
                try {
                    for (MessageExt msg : msgs) {
                        System.out.printf(Thread.currentThread().getName() + " Receive New Messages: " +  new String(msg.getBody()) + "%n");
                    }
                }catch (Exception e){
                    e.printStackTrace();
                    return ConsumeConcurrentlyStatus.RECONSUME_LATER;//重试
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; //成功
            }
        });

        /*
         *  Consumer对象在使用之前必须要调用start初始化,初始化一次即可
         */
        consumer.start();

        System.out.printf("transaction_Consumer  Started.%n");
    }
}

运行结果:

Springboot rocketMq 消费者 手动ack rocketmq命令行消费消息_System

消费者只收到了成功的消费

Springboot rocketMq 消费者 手动ack rocketmq命令行消费消息_System_02

rocketmq事务消费先记到这里,后面接触到分布式事务再深入研究 end!