什么是事务消息
事务消息用于解决分布式系统中的事务问题,不了解分布式事务的请自行Google。
通常分布式事务可以使用两阶段,三阶段,TCC,XA,本地事务表等方式来实现强一致性或者最终一致性事务。
这里rocketmq的事务消息就是采用的最终一致性解决的分布式事务。
分布式事务的两个参与者,一方参与者通过事务消息保证本地事务执行结果与MQ中的消息一致,要么都成功,要么都失败回滚。
另一个参与者则消费MQ中的消息,注意offset的提交,需要保证消费不丢失以及支持幂等。
rocketmq的执行流程如下:
接下来看一下使用事务消息。
生产者
与之前的生产者不同,这里使用的是支持事务的生产者TransactionMQProducer
,并且需要编写一个监听器transactionListener
,用于执行本地事务的提交或MQ的broker在超时获取不到提交或回滚指令后检查消息状态。
public static void main(String[] args) throws MQClientException, InterruptedException {
TransactionListener transactionListener = new TransactionListenerImpl();
TransactionMQProducer producer = new TransactionMQProducer("please_rename_unique_group_name");//这里使用的是支持事务的TransactionMQProducer
producer.setNamesrvAddr("node1:9876");
ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2000), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("client-transaction-msg-check-thread");
return thread;
}
});
producer.setExecutorService(executorService);
producer.setTransactionListener(transactionListener);//配置监听器
producer.start();
String[] tags = new String[]{"TagA", "TagB", "TagC", "TagD", "TagE"};
for (int i = 0; i < 10; i++) {
try {
Message msg =
new Message("TopicTest", tags[i % tags.length], "KEY" + i,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult sendResult = producer.sendMessageInTransaction(msg, null);
System.out.printf("%s%n", sendResult);
Thread.sleep(10);
} catch (MQClientException | UnsupportedEncodingException e) {
e.printStackTrace();
}
}
TimeUnit.SECONDS.sleep(60);
producer.shutdown();
}
public class TransactionListenerImpl implements TransactionListener {
private AtomicInteger transactionIndex = new AtomicInteger(0);
private ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<>();
//执行本地事务
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
System.out.println("executeLocalTransaction");
int value = transactionIndex.getAndIncrement();
int status = value % 3;//这里模拟失败超时等场景
localTrans.put(msg.getTransactionId(), status);
return LocalTransactionState.UNKNOW;
}
//检查本地事务执行结果
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
Integer status = localTrans.get(msg.getTransactionId());
System.out.println("checkLocalTransaction" + msg + " "+status);
if (null != status) {
switch (status) {
case 0:
return LocalTransactionState.UNKNOW;
case 1:
return LocalTransactionState.COMMIT_MESSAGE;
case 2:
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
return LocalTransactionState.COMMIT_MESSAGE;
}
}
消费者
这里还是采用之前的普通消费者
public static void main(String[] args) throws Exception {
normal();//普通消费
// order();//顺序消费
}
private static void normal() throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("example_group_name");
consumer.setNamesrvAddr("node1:9876");
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
consumer.subscribe("TopicTest", "*");
consumer.setConsumeThreadMin(3);
consumer.setConsumeThreadMax(6);
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
for (MessageExt msg : msgs) {
// System.out.println("收到消息," + new String(msg.getBody()));
System.out.println("queueId:"+msg.getQueueId()+",orderId:"+new String(msg.getBody())+",i:"+msg.getKeys());
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();
System.out.printf("Consumer Started.%n");
}
先启动消费者等待消费数据。
然后启动生产者,生产消息。注意这里在执行本地事务时模拟了提交回滚未知等情况,所以实际成功的消息有7条,大家可以根据msgId来查看生产者成功的数据是否与消费者收到的数据一致。