1.RocketMQ有三种消息
1)普通消息
2)顺序消息
3)事务消息
2.分布式事务-强调最终一致性而不是强一致性
- 单个应用,数据库分库分表了(跨数据库)
- 多个应用,服务拆分了,不跨数据库
- 多个应用,服务拆分了,并且数据库分库分表了
正式因为出现了上面三种情况,才引入了分布式事务的解决方案,而RocketMQ的事务消息就是一种解决方案
3.RocketMQ事务消息的执行流程
事务消息有三种状态:
- UNKNOWN状态:表示事务消息未确定,可能是业务方执行本地事务逻辑时间耗时过长或者网络原因等引起的,该状态会导致步骤5的发生
- COMMIT状态:表示事务消息被提交,会被正确分发给消费者。
- ROLLBACK状态:该状态表示该事务消息被回滚,因为本地事务逻辑执行失败导致
4.生产者发送prepare消息的过程
5.boroker处理prepare的消息过程
6.broker结束事务消息的过程
7.broker对事务消息是如何回查的
8.事务消息的异常恢复机制
事务消息的异常状态主要有:
生产者提交prepare消息到broker成功,但是当前生产者实例宕机了
生产者提交prepare消息到broker失败,可能是因为提交的broker已宕机
生产者提交prepare消息到broker成功,执行本地事务逻辑成功,但是broker宕机了未确定事务状态
生产提交prepare消息到broker成功,但是在进行事务回查的过程中broker宕机了,未确定事务状态
对于1:事务消息会根据producerGroup搜寻其他的生产者实例进行回查,所以transactionId务必保存在中央存储中,并且事务消息的pid不能跟其他消息的pid混用。
对于2:当前实例会搜寻其他的可用的broker-master进行提交,因为只有提交prepare消息后才会执行本地事务,所以没有影响,注意生产者报的是超时异常时,是不会进行重发的。
对于3:因为返回状态是oneway方式,此时如果消费者未收到消息,需要用手段确定该事务消息的状态,尽快将broker重启,broker重启后会通过回查完成事务消息。
对于4:同3,尽快重启broker。
9.生产者代码示例
package com.roger.transaction.producer;
import com.roger.transaction.listener.TransactionListenerImpl;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import java.util.UUID;
import java.util.concurrent.*;
public class TransactionProducer {
public static void main(String[] args) throws Exception {
//step1:未决事务
// 也就是 当RocketMQ发现`Prepared消息`时,会根据这个Listener实现的策略来决断事务
TransactionListener transactionListener = new TransactionListenerImpl();
//step2:创建事务消息生产者
TransactionMQProducer transactionMQProducer =
new TransactionMQProducer("transactionProducerGroup");
//step3:设置nameServer地址
transactionMQProducer.setNamesrvAddr("172.20.10.60:9876");
//step4:设置事务消息的决断者
transactionMQProducer.setTransactionListener(transactionListener);
//step6:设置事务消息的处理线程池
ExecutorService executorService = new ThreadPoolExecutor(2,
5,
30,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("trancationMQProducerThread");
return thread;
}
});
transactionMQProducer.setExecutorService(executorService);
//step7:启动
transactionMQProducer.start();
//step8:发送消息
String[] tags = new String[]{"TagTMA", "TagTMB", "TagTMC"};
for (int i = 0; i < 20; i++) {
Message msg = new Message();
msg.setTopic("TrancationMsgTopic");
msg.setTags(tags[i % tags.length]);
msg.setKeys(UUID.randomUUID().toString());
msg.setBody("Hello RocketMQ Tranaction Msg".getBytes(RemotingHelper.DEFAULT_CHARSET));
TransactionSendResult sendResult = transactionMQProducer.sendMessageInTransaction(msg, null);
System.out.printf("%s%n", sendResult);
}
}
}
package com.roger.transaction.listener;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 事务消息的检测机制
*/
public class TransactionListenerImpl implements TransactionListener {
private ConcurrentMap<String,Integer> localTransMap = new ConcurrentHashMap<>();
private AtomicInteger transactionIndex = new AtomicInteger(0);
/**
* 1)设置本地事务状态
* 2)在本地事务表中插入(t_message_transaction)一条记录
* 3)临时使用ConcurrentMap可以实现存储
* 4)这个方法和业务方法的代码在一个事务中,即业务方法成功,会在本地事务表中增加一条记录,否则,不会增加
* @param msg
* @param args
* @return
*/
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object args) {
int value = transactionIndex.getAndIncrement();
int status = value % 3;
localTransMap.put(msg.getTransactionId(),status);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(transactionIndex.get());
return LocalTransactionState.UNKNOW;
}
/**
* 告知RocketMQ消息是需要提交还是回滚
*
* broker定时进行消息会查
*
* 如果 executeLocalTransaction()方法返回的消息状态是UNKNOWN状态的消息
* 则 broker会调用checkLocalTransaction()方法检验这个这个事务消息状态
* 来告知RocketMQ消息是需要提交还是回滚
* @param msgExt
* @return
*/
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msgExt) {
System.out.println("broker 会查事务状态....");
Integer status = localTransMap.get(msgExt.getTransactionId());
if(status == null){
return LocalTransactionState.ROLLBACK_MESSAGE;
}
switch (status){
case 0:
return LocalTransactionState.UNKNOW;
case 1:
return LocalTransactionState.COMMIT_MESSAGE;
case 2:
return LocalTransactionState.ROLLBACK_MESSAGE;
}
return LocalTransactionState.UNKNOW;
}
}
参考博文:https://blog.csdn.net/qq_28632173/article/details/83790243