RocketMQ提供局部顺序一致性的机制,Broker为每个Topic每个QueueID构建消息队列(ConsumerQueue),实现了单个队列中的消息严格有序。按照业务划分消息队列,然后将需要顺序消费的消息发往同一队列,消费者根据消息队列拉取消息进行消费,即可做到顺序消费。
顺序消费原理
1.生产者Producer端:生产端实现有序性,依赖于MessageQueue选择能力,RocketMQ提供MessageQueueSelector接口用于消息队列选取。
2.服务端Broker:Broker为每个Topic每个QueueID构建ConsumerQueue。
3.消费者Consumer端:消费端提供监听器MessageListenerOrderly接口,消费者需要依据业务要求实现这个接口即可。
以上是RocketMQ提供的机制,下面我们一起看一下源码。
源码常看
1.生产端Producer端,生产端提供MessageQueueSelector接口,同时提供了根据hashCode,MachineRoom,Randoom三种选择方式;提供了selectOneMessageQueue实现选择MessageQueue的方法,我们需要根据自己的业务需求实现。
public interface MessageQueueSelector {
MessageQueue select(final List<MessageQueue> mqs, final Message msg, final Object arg);
}
1.1SelectMessageQueueByHash
public class SelectMessageQueueByHash implements MessageQueueSelector {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
int value = arg.hashCode();
if (value < 0) {
value = Math.abs(value);
}
value = value % mqs.size();
return mqs.get(value);
}
}
1.2SelectMessageQueueByMachineRoom
public class SelectMessageQueueByMachineRoom implements MessageQueueSelector {
private Set<String> consumeridcs;
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
return null;
}
public Set<String> getConsumeridcs() {
return consumeridcs;
}
public void setConsumeridcs(Set<String> consumeridcs) {
this.consumeridcs = consumeridcs;
}
}
1.3SelectMessageQueueByRandoom
public class SelectMessageQueueByRandoom implements MessageQueueSelector {
private Random random = new Random(System.currentTimeMillis());
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
int value = random.nextInt();
if (value < 0) {
value = Math.abs(value);
}
value = value % mqs.size();
return mqs.get(value);
}
}
1.4生产者发送消息提供的默认实现DefaultMQProducerImpl中有多个根据selector发送消息的实现。我们根据要求调用即可。
public class DefaultMQProducerImpl implements MQProducerInner {
private SendResult sendKernelImpl(final Message msg, //
final MessageQueue mq, //
final CommunicationMode communicationMode, //
final SendCallback sendCallback, //
final TopicPublishInfo topicPublishInfo, //
final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
………省略实现代码…………
}
}
2.消费端Consumer,消费端提供MessageListenerOrderly接口,具体的业务逻辑需要我们自己实现
public interface MessageListenerOrderly extends MessageListener {
/**
* It is not recommend to throw exception,rather than returning ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT if consumption failure
*
* @param msgs
* msgs.size() >= 1<br>
* DefaultMQPushConsumer.consumeMessageBatchMaxSize=1锛寉ou can modify here
* @param context
*
* @return
*/
ConsumeOrderlyStatus consumeMessage(final List<MessageExt> msgs,
final ConsumeOrderlyContext context);
}
总结:
RocketMQ的顺序消费是相对性的,发送消息在分布式环境下无法保证broker接收消息的顺序;broker无法根据收到的消息顺序,构建正确的MessageQueue顺序;消费者在分布式环境下也无法保证获取到消息的顺序性,业务实现由于业务复杂度不一样,也不能保证获取到的消息能按顺序处理完。