事务消息
事务消息是一种在发送方和接收方之间保证消息传递的一致性和可靠性的消息传递机制。在消息发送过程中,生产者可以将消息发送到消息队列,但不会立即被消费者接收和处理。相反,消息会先进入一种“准备”状态,然后等待生产者确认消息的状态,最后再决定是否将消息提交给消费者。
流程图:
可以看到,事务消息主要分为两个流程:
(1) 正常事务消息的发送及提交
a、生产者发送half消息到Broker服务端(半消息);
半消息是一种特殊的消息类型,该状态的消息暂时不能被Consumer消费。当一条事务消息被成功投递到Broker上,但是Broker并没有接收到Producer发出的二次确认时,该事务消息就处于"暂时不可被消费"状态,该状态的事务消息被称为半消息。
b、Broker服务端将消息持久化之后,给生产者响应消息写入结果(ACK响应);
c、生产者根据发送结果执行本地事务逻辑(如果写入失败,此时half消息对业务不可见,本地逻辑不执行);
d、生产者根据本地事务执行结果向Broker服务端提交二次确认(Commit 或是 Rollback),Broker服务端收到 Commit 状态则将半事务消息标记为可投递,订阅方最终将收到该消息;Broker服务端收到 Rollback 状态则删除半事务消息,订阅方将不会接收该消息;
(2) 事务消息的补偿流程
a、在网络闪断或者是应用重启的情况下,可能导致生产者发送的二次确认消息未能到达Broker服务端,经过固定时间后,Broker服务端将会对没有Commit/Rollback的事务消息(pending状态的消息)进行“回查”;
b、生产者收到回查消息后,检查回查消息对应的本地事务执行的最终结果;
c、生产者根据本地事务状态,再次提交二次确认给Broker,然后Broker重新对半事务消息Commit或者Rollback;
其中,补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。
事务消息共有三种状态,提交状态、回滚状态、中间状态:
TransactionStatus.CommitTransaction:提交事务,它允许消费者消费此消息。
TransactionStatus.RollbackTransaction:回滚事务,它代表该消息将被删除,不允许被消费。
TransactionStatus.Unknown:中间状态,它代表需要回查本地事务状态来决定是提交还是回滚事务。
参考案例
1、生产者:
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.common.message.MessageExt;
import org.apache.rocketmq.client.producer.LocalTransactionExecuter;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
public class TransactionMessageProducer {
public static void main(String[] args) throws Exception {
// 实例化事务消息生产者
TransactionMQProducer producer = new TransactionMQProducer("transaction_producer_group");
// 设置NameServer地址
producer.setNamesrvAddr("localhost:9876");
// 注册事务监听器
producer.setTransactionListener(new TransactionListener() {
// 执行本地事务
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
// 执行本地事务,例如数据库操作等
// 返回COMMIT或ROLLBACK或UNKNOWN状态
return LocalTransactionState.COMMIT_MESSAGE;
}
// 检查本地事务状态
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
return LocalTransactionState.COMMIT_MESSAGE;
}
});
// 启动生产者
producer.start();
// 创建消息
Message message = new Message("transaction_topic", "Hello Transaction RocketMQ".getBytes());
// 发送事务消息
TransactionSendResult sendResult = producer.sendMessageInTransaction(message, null);
System.out.printf("%s%n", sendResult);
// 关闭生产者
producer.shutdown();
}
}
2、消费者:
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
public class TransactionMessageConsumer {
public static void main(String[] args) throws InterruptedException, MQClientException {
// 实例化消费者
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("transaction_consumer_group");
// 设置NameServer地址
consumer.setNamesrvAddr("localhost:9876");
// 订阅Topic
consumer.subscribe("transaction_topic", "*");
// 注册消息监听器
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
for (MessageExt msg : msgs) {
System.out.printf("Receive message[msgId=%s] from %s%n", msg.getMsgId(), msg.getTopic());
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
// 启动消费者
consumer.start();
System.out.printf("Transaction Consumer Started.%n");
}
}
ps:以下是我整理的java面试资料,感兴趣的可以看看。最后,创作不易,觉得写得不错的可以点点关注!