什麼是事務消息
事務消息用於解決分佈式系統中的事務問題,不瞭解分佈式事務的請自行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來查看生產者成功的數據是否與消費者收到的數據一致。