前言
在使用rocketmq的時候如果要保證嚴格的順序,那麼就需要將消息發送到rocketmq的一個消息隊列中,由於一個消息隊列只能在一個broker上,可能處出現短暫的不可用性(當節點的一個master發生主備切換時)。
1. producer demo
使用rocketmq實現的SelectMessageQueueByHash
package com.feng.rocketmq.base;
import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.selector.SelectMessageQueueByHash;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;
import java.io.UnsupportedEncodingException;
public class OrderedProducerDefaultMode {
public static void main(String[] args) throws InterruptedException, RemotingException, MQClientException, MQBrokerException, UnsupportedEncodingException {
//instance
DefaultMQProducer producer = new DefaultMQProducer();
//group, for producer load balance
producer.setProducerGroup("demo-producer-group");
//namesrvAddr,cluster nameserver with ; spit
producer.setNamesrvAddr("127.0.0.1:9876");
//start
producer.start();
// send msg
int num = 20;
for (int i = 0; i < num; i++) {
//構建實例,第一個參數爲topic,第二個參數爲tabs,第三個參數爲消息體
Message message = new Message("demoTopic","tags-1", "instanceKeys", ("I`m a " + i + " rocket mq msg!").getBytes(RemotingHelper.DEFAULT_CHARSET));
Integer id = i/5;
SendResult result = producer.send(message, new SelectMessageQueueByHash(), id);
System.out.println("send result is\t" + result);
}
//close, can use for rocket mq switch
producer.shutdown();
}
}
通過對參數hash取模隊列總數,分發到某一條消息隊列
默認3種實現
當然也可以自己實現邏輯,要保證
producer --> messagequeue --> consumer 完整的一對一關係,才能保證消息的順序發送;當然我們只需要保證消息的局部一致性就可以了,全局一致性不需要保證,否則性能會大幅度下降,也是不需要這種實際場景的。
我們可以根據自己的業務自己實現
SendResult result = producer.send(message, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
return mqs.get((Integer) arg % mqs.size());
}
}, 1);
2. consumer
public class OrderedPushConsumer {
public static void main(String[] args) throws MQClientException {
//instance
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer();
//group
consumer.setConsumerGroup("demo-consumer-group");
//setNamesrvAddr,cluster with ; spit
consumer.setNamesrvAddr("127.0.0.1:9876");
//consumer offset
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
//subscribe, the subExpression is tags in message send
//subscribe topic store in map
consumer.subscribe("demoTopic", "tags-1");
//can subscribe more
//consumer.subscribe("demoTopic2", "*");
//or use setSubscription, method is deprecated
//consumer.setSubscription();
//batch consumer max message limit
consumer.setConsumeMessageBatchMaxSize(1000);
//min thread
consumer.setConsumeThreadMin(10);
//listener, MessageListenerOrderly for one messagequeue can only consumed by one thread
consumer.registerMessageListener((MessageListenerOrderly) (list, consumeOrderlyContext) -> {
try {
for (MessageExt messageExt : list) {
if (messageExt.getReconsumeTimes() > 1){
continue;
}
String topic = messageExt.getTopic();
int queueId = messageExt.getQueueId();
String message = new String(messageExt.getBody(), RemotingHelper.DEFAULT_CHARSET);
System.out.println("the topic: " + topic + "\tqueueId:" + queueId + "\t body:" + message);
}
} catch (Exception e) {
//retry
return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
}
return ConsumeOrderlyStatus.SUCCESS;
});
consumer.start();
//consumer.shutdown();
}
}
多個隊列之間不保證順序,單個隊列保證順序。如果consumer集羣的某一個節點宕機或者故障導致消費消息未提交,可能導致重複消費
先看看MessageListenerOrderly,使用線程池處理推送消息,使用lock鎖保證消息隊列被一個線程處理
3. 多個consumer的重複消費需要自行處理
再起一個consumer端,同一group
消費時,退出進程
確實會重複消費,原因很明顯了,狀態未提交,rocketmq不知道consumer是否消費了,需要自行去重或者冪等設計
總結
rocketmq原生提供通過Hash取模算法的順序一致性,保證局部一致性。性能較強,滿足業務需求,還可以定製算法,提供靈活擴展性。消費者不保證唯一消費,需要自行處理。rocketmq還可以支持分佈式事務,下一章說明。