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