首先为什么要保证消息的顺序消费?
1、业务的逻辑的依赖性:某些业务的逻辑需要要求消息顺序消费执行,比如订单的下单过程,每一步都需要按照步骤执行,以保证顺序的一致性
2、数据一致性:消息的顺序消费可以保证数据一致性,避免数据的错乱。比如一些业务的执行按照消息顺序执行存储数据过程,那么就可以避免并发上带来的数据一致性问题
3、依赖多个消息:一些业务上会存在多个消息同时执行的情况,那么就需要保证这些消息的正确顺序执行。比如电商上订单创建、付款请求、完成订单确认,这三个步骤都会产生消息发送到MQ内,那么就必须保证这些消息的顺序执行
那么如何保证消息的顺序消费呢?
1、全局有序:消费的所有消息都严格按照发送消息的顺序执行
这个是用一个for循环将所有消息放在同一个消息队列中,这样保证消息的顺序消费
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import java.util.List;
public class OrderProducer {
public static void main(String[] args) throws Exception {
// 创建一个RocketMQ的消息生产者实例
DefaultMQProducer producer = new DefaultMQProducer("OrderProducerGroup");
// 设置NameServer的地址
producer.setNamesrvAddr("localhost:9876");
// 启动生产者实例
producer.start();
// 发送消息
List messages = generateMessages();
for (Message message : messages) {
// 使用MessageQueueSelector来选择消息队列
SendResult sendResult = producer.send(
message,
new MessageQueueSelector() {
@Override
public MessageQueue select(List mqs, Message msg, Object arg) {
// 这里可以根据自定义逻辑选择要发送的消息队列,确保消息按照正确的顺序发送
return mqs.get(0); // 这里简单地选择第一条消息队列
}
},
null);
System.out.printf("SendResult: %s%n", sendResult);
}
// 关闭生产者实例
producer.shutdown();
}
private static List generateMessages() throws Exception {
// 生成一些有序的消息示例
List messages = new ArrayList<>();
messages.add(new Message("TopicTest", "TagA", "Order001", "Hello RocketMQ 1".getBytes(RemotingHelper.DEFAULT_CHARSET)));
messages.add(new Message("TopicTest", "TagA", "Order002", "Hello RocketMQ 2".getBytes(RemotingHelper.DEFAULT_CHARSET)));
messages.add(new Message("TopicTest", "TagA", "Order003", "Hello RocketMQ 3".getBytes(RemotingHelper.DEFAULT_CHARSET)));
return messages;
}
}
2、局部有序:消息的部分消息按照发送顺序执行
局部消费意味着消息给不同的消费者并行地消费,但在每个消费者内部保证顺序。
在局部顺序下,消息可能会被分发到不同的消费者实例中,每个消费者实例负责处理特定的消息,在每个消费者内部,消费者线程会按照消息顺序消费,保证每个实例内部的消费是顺序的。这样设计可以提高整体的消息处理并发性。
那么什么时候适合局部有序呢?
比如订单消息可以被分为“支付”和“发货”;那这两个消息可以被发给不同的消费者去消费,这样分组的消息可以被顺序消费,也可以保证整体的消息的顺序
下面的例子
import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.remoting.common.RemotingHelper; public class LocalConsumer { public static void main(String[] args) throws Exception { // 创建一个RocketMQ的消息生产者实例 DefaultMQProducer producer = new DefaultMQProducer("LocalProducerGroup"); // 设置NameServer的地址 producer.setNamesrvAddr("localhost:9876"); // 启动生产者实例 producer.start(); // 发送消息 producer.send(new Message("TopicTest", "TagA", "Order001", "Payment message".getBytes(RemotingHelper.DEFAULT_CHARSET))); producer.send(new Message("TopicTest", "TagB", "Order002", "Shipping message".getBytes(RemotingHelper.DEFAULT_CHARSET))); producer.send(new Message("TopicTest", "TagA", "Order003", "Payment message".getBytes(RemotingHelper.DEFAULT_CHARSET))); // 关闭生产者实例 producer.shutdown(); } }
保证消息的顺序消费就必须保证以下两点
1、单线程消费:消息中间件可能会将一个topic下的消息分配给同一个消费者线程来处理,确保一个消费者线程只处理一个消息。这样可以确保消息的顺序消费,但会限制并发能力,因为所有的消息都被一个消费者消费
2、分区顺序消费:一些消息中间件支持分区,可以将消息分散到不同的分区中。在顺序消费的情况下,中间件可能会将同一个分区的消息发送给同一个消费者实例来处理,从而保证消息在分区内的顺序消费。但这种方式也会限制并发处理性能,因为消息被分配给不同的消费者实例进行顺序消费。