RocketMQ提供了3種模式的Producer:NormalProducer(普通)、OrderProducer(順序)、TransactionProducer(事務),對應的分別是普通消息、順序消息和事務消息。在前面的博客當中,涉及的都是NormalProducer,調用傳統的send方法,消息是無序的。接下來,看看順序消費。
模擬這樣一個場景,如果一個用戶完成一個訂單需要3條消息,比如訂單的創建、訂單的支付、訂單的發貨,很顯然,同一個用戶的訂單消息必須要順序消費,但是不同用戶之間的訂單可以並行消費。
生產者端
看一下生產者端的代碼:
DefaultMQProducer producer = new DefaultMQProducer("OrderProducer");
producer.setNamesrvAddr("192.168.99.9876");
producer.start();
String[] tags = new String[]{"createTag", "payTag", "sendTag"};
for (int orderId = 1; orderId <= 10; orderId++) { //訂單消息
for (int type = 0; type < 3; type++) { //每種訂單分爲:創建訂單/支付訂單/發貨訂單
Message msg = new Message("OrderTopic",
tag[type % tag.length],
orderId + ":" + type,
(orderId + ":" + type).getBytes()
);
SendResult sendResult = producer.send(msg, new MessageQueueSelector(){
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg){
Integer id = (Integer) arg;
int index = id % mqs.size();
return mqs.get(index);
}
}, orderId);
System.out.println(sendResult);
}
}
注意:一個Message除了Topic/Tag外,還有Key的概念。
上圖的send方法不同於以往,有一個MessageQueueSelector,將用於指定特定的消息發往特定的隊列當中!
消費者端
看一下消費者端的代碼:
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
try {
//模擬業務處理消息的時間
Thread.sleep(new Random().nextInt(1000));
System.out.println(new String(msgs.getBody(),"utf-8"));
} catch (Exception e) {
e.printStackTrace();
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
注意:在以前普通消費消息時設置的回調是MessageListenerConcurrently,而順序消費的回調設置是MessageListenerOrderly。
運行效果
當我們啓動2個Consumer進行消費時,可以觀察到:
可以觀察得到,雖然從全局上來看,消息的消費不是有序的,但是每一個訂單下的3條消息是順序消費的!
其實,如果需要保證消息的順序消費,那麼很簡單,首先需要做到一組需要有序消費的消息發往同一個broker的同一個隊列上!其次消費者端採用有序Listener即可。
補充
這裏,RocketMQ底層是如何做到消息順序消費的,看一看源碼你就能大概瞭解到,至少來說,在多線程消費場景下,一個線程只去消費一個隊列上的消息,那麼自然就保證了消息消費的順序性,同時也保證了多個線程之間的併發性。也就是說其實broker並不能完全保證消息的順序消費,它僅僅能保證的消息的順序發送而已!
關於多線程消費這塊,RocketMQ早就替我們想好了,這樣設置即可:
想一想,在ActiveMQ中,我們如果想實現併發消費的話,恐怕還得搞個線程池提交任務吧,RocketMQ讓我們的工作變得簡單!