前言
上節我們介紹了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處理:
好了,關於順序消費的問題就先到這了,這個問題後面我再去查閱相關資源看看到底是什麼原因?今天先到這,感謝你的關注,感謝你的閱讀!!!