RocketMQ系列之順序消費

前言

上節我們介紹了RMQ的兩大亮點,重試和重複消費的問題,其實重試才能算亮點,重複消費最終還是要由我們自己來解決這個問題,RMQ自身並沒有提供很好的機制,至少目前是沒有,不知道將來會不會有,OK扯遠了,今天呢,我們再來介紹RMQ一個不錯的地方,那就是順序消費,RMQ是可以保證同一個queue中的消息被順序的消費。

 

RMQ實現如何實現順序消費?

生產者Producer在生產消息時將需要順序消費的消息發送到同一個queue下,每個topic默認是有4個queue所以Producer需要一個隊列選擇器來進行queue的選擇;

消費者Consumer端在進行消息的消費時,消費者註冊的消息監聽器就不是之前的MessageListenerConcurrently,而是換成MessageListenerOrderly,這樣就可以保證消費者只有一個線程去處理該消息;

 

Producer端如何操作?

生產端保證將消息發送到topic下同一個隊列中即可:我們發送了8條消息到座標爲0的隊列中:

public class Producer {
   public static void main(String[] args) throws MQClientException, InterruptedException {
       // 聲明一個生產者,需要一個自定義生產者組(後面我們會介紹這個組的概念和作用)
       DefaultMQProducer producer = new DefaultMQProducer("myTestGroup");
       // 設置集羣的NameServer地址,多個地址之間以分號分隔
       producer.setNamesrvAddr("");
       // 如果消息發送失敗就進行5次重試
       producer.setRetryTimesWhenSendFailed(5);
       // 啓動生產者實例
       producer.start();

       for (int i = 0; i < 8; i++) {
           Message msg = new Message("TopicTest", "order_1", "key" + i, ("order_1" + i).getBytes());
           // 調用Produce的send方法發送消息
           try {
               // 發送消息並構建一個queue選擇器,保證消息都進入到同一個隊列中
               SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
                   // 重寫了MessageQueueSelector 的select方法
                   @Override
                   public MessageQueue select(List<MessageQueue> list, Message msg, Object arg) {
                       Integer id = (Integer) arg;
                       return list.get(id);
                   }
               }, 0);// 隊列的下標
               System.out.println(sendResult);
           } catch (Exception e) {
               e.printStackTrace();
           }
       }
       // 關閉
       producer.shutdown();
   }
}

 

Consumer端

Consumer註冊MessageListenerOrderly監聽即可:

public class Consumer {
   public static void main(String[] args) throws InterruptedException, MQClientException {
       // 聲明一個消費者consumer,需要傳入一個組
       DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumerTest");
       // 設置集羣的NameServer地址,多個地址之間以分號分隔
       consumer.setNamesrvAddr("");
       // 設置consumer的消費策略
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
       // 集羣模式消費,廣播消費不會重試
        consumer.setMessageModel(MessageModel.CLUSTERING);
       // 設置最大重試次數,默認是16次
       //consumer.setMaxReconsumeTimes(5);
       // 設置consumer所訂閱的Topic和Tag,*代表全部的Tag
       consumer.subscribe("TopicTest", "*");
       // Listener,主要進行消息的邏輯處理,監聽topic,如果有消息就會立即去消費
       consumer.registerMessageListener(new MessageListenerOrderly() {
           @Override
           public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext consumeOrderlyContext) {
               try {
                   MessageExt messageExt = msgs.get(0);
                   String msgBody = new String(messageExt.getBody(),"utf-8");
                   System.out.println(" 接收新的消息:消息內容爲:"+msgBody);
               } catch (Exception e) {
                   e.printStackTrace();
                   System.out.println(e);
               }
               return ConsumeOrderlyStatus.SUCCESS;
           }
       });
       // 調用start()方法啓動consumer
       consumer.start();
       System.out.printf("Consumer1 啓動.%n");
   }
}

 

OK,我們先看下目前MQ上消息情況如下圖:

 

我們依次啓動消費者和生產者:

我們在看下控制檯消息情況:8條消息出入記錄

 

到這裏可能有的小夥伴就會問了,你消息都發送到同一個隊列,那如果我發2個隊列,會是什麼情況呢?我們把生產者改造下:生產者往下標0和3的隊列分別發送4條消息:

for (int i = 0; i < 4; i++) {
           Message msg = new Message("TopicTest", "order_1", "key" + i, ("order_1" + i).getBytes());
           // 調用Produce的send方法發送消息
           try {
               // 發送消息並構建一個queue選擇器,保證消息都進入到同一個隊列中
               SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
                   // 重寫了MessageQueueSelector 的select方法
                   @Override
                   public MessageQueue select(List<MessageQueue> list, Message msg, Object arg) {
                       Integer id = (Integer) arg;
                       return list.get(id);
                   }
               }, 0);// 隊列的下標
               System.out.println(sendResult);
           } catch (Exception e) {
               e.printStackTrace();
           }
       }
       for (int i = 0; i < 4; i++) {
           Message msg = new Message("TopicTest", "order_1", "key2" + i, ("order_2" + i).getBytes());
           // 調用Produce的send方法發送消息
           try {
               // 發送消息並構建一個queue選擇器,保證消息都進入到同一個隊列中
               SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
                   // 重寫了MessageQueueSelector 的select方法
                   @Override
                   public MessageQueue select(List<MessageQueue> list, Message msg, Object arg) {
                       Integer id = (Integer) arg;
                       return list.get(id);
                   }
               }, 3);// 隊列的下標
               System.out.println(sendResult);
           } catch (Exception e) {
               e.printStackTrace();
           }
       }

 

我們再來看下消費者端是怎麼消息的,是否保持順序消費?

可能會出現上面2種結果:不管是第一種還是第二種結果,雖然第二種結果整體上不是有序的,但是仔細看每個每列中的消息,發現都是有序的,這也證明是有序消費指的是在同一個queue下而不是topic,針對的是隊列;

其實MessageListenerOrderly設計就是不允許你在消費消息時啓動多個線程去消費,這是設計上就不允許的;

 

還有一種情況就是啓動多個consumer,同時消費,網上流傳的版本是多個consumer會分別處理多個不同queue下的數據,我本地是沒有測試出來,我試了N次的結果都是啓動多個consumer時,只有一個consumer會去消費掉所有的消息,不知道是不是我使用的是新版本RMQ的原因還是別的原因,按道理來說一個組下的consumer是會負載均衡的去消費的,這點我後面再看看。

這是我執行的結果:我分別向4個queue發送了消息,都只會被一個consumer處理:

 

 

好了,關於順序消費的問題就先到這了,這個問題後面我再去查閱相關資源看看到底是什麼原因?今天先到這,感謝你的關注,感謝你的閱讀!!!

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