一、主要场景
事物消费需要先说说什么是分布式事务。比如经典的跨行转账:从工商银行转到建设银行,也就是我从工商银行扣除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");
}
}
运行结果:
消费者只收到了成功的消费
rocketmq事务消费先记到这里,后面接触到分布式事务再深入研究 end!