6. rocketmq事務消息

什麼是事務消息

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章