文章目录
前提
创建一个 Maven 的 Java 工程。
引入 RocketMQ 的 Client 依赖,版本需要和服务端一致。
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.9.0</version>
</dependency>
二、顺序消息
顺序消息(FIFO 消息),顺序消息指的是,严格按照消息的 发送顺序
进行消费的消息。
默认情况下生产者会把消息以 Round Robin 轮询方式发送到不同的 Queue 分区队列;而消费消息时会从多个 Queue 上拉取消息,这种情况下的发送和消费是不能保证顺序的。如果将消息仅发送到同一个 Queue 中,消费时也只从这个 Queue 上拉取消息,就严格保证了消息的顺序性。
1、有序性分类
根据有序范围的不同,RocketMQ 可以严格地保证两种消息的有序性:分区有序
与 全局有序
- 全局顺序:对于指定的一个 Topic,所有消息按照严格的先入先出 FIFO(First In First Out)的顺序进行发布和消费。
- 分区顺序:对于指定的一个 Topic,所有消息根据 Sharding Key 进行区块分区。同一个分区内的消息按照严格的 FIFO 顺序进行发布和消费。Sharding Key 是顺序消息中用来区分不同分区的关键字段,和普通消息的 Key 是完全不同的概念。
2、全局有序
当发送和消费参与的 Queue 只有一个时所保证的有序是整个 Topic 中消息的顺序,称为全局有序
在创建 Topic 时指定 Queue 的数量。有三种指定方式:
- 在代码中创建 Producer 时,可以指定其自动创建的 Topic 的 Queue 数量
- 在 RocketMQ 可视化控制台中手动创建 Topic 时指定 Queue 数量
- 使用 mqadmin 命令手动创建 Topic 时指定 Queue 数量
3、分区有序
如果有多个 Queue 参与,其仅可保证在该 Queue 分区队列上的消息顺序,则称为分区有序
在定义 Producer 时我们可以指定消息队列选择器,而这个选择器是我们自己实现了 MessageQueueSelector 接口定义的。
在定义选择器的选择算法时,一般需要使用选择 key。这个选择 key 可以是消息 key 也可以是其它数据。但无论谁做选择 key,都不能重复,都是唯一的。
4、发送顺序消息
全局顺序消息和分区顺序消息的发布方式基本一样
package com.learn.rocketmq.order;
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 OrderedProducer {
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("pg");
producer.setNamesrvAddr("localhost:9876");
// 若为全局有序,则需要设置Queue数量为1
// producer.setDefaultTopicQueueNums(1);
producer.start();
for (int i = 0; i < 100; i++) {
// 演示简单,使用整型数作为orderId
Integer orderId = i;
byte[] body = ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET);
Message msg = new Message("LearnTopic", "DonTag-Ordered", body);
// 将orderId作为消息key
msg.setKeys(orderId.toString());
// send()的第三个参数值会传递给选择器的select()的第三个参数
// 该send()为同步发送
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
// 具体的选择算法在该方法中定义
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
// 以下是使用消息key作为选择的选择算法
String keys = msg.getKeys();
Integer id = Integer.valueOf(keys);
// 以下是使用arg作为选择key的选择算法
// Integer id = (Integer) arg;
int index = id % mqs.size();
return mqs.get(index);
}
}, orderId);
System.out.println(sendResult);
}
producer.shutdown();
}
}
5、订阅顺序消息
全局顺序消息和分区顺序消息的订阅方式基本一样
package com.learn.rocketmq.order;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.*;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
public class OrderedConsumer {
public static void main(String[] args) throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("cg");
consumer.setNamesrvAddr("localhost:9876");
// 指定从第一条消息开始消费
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
// 指定消费topic与tag
consumer.subscribe("LearnTopic", "*");
// 注册消息监听器
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
// 逐条消费消息
for (MessageExt msg : list) {
System.out.println(msg);
}
// 返回消费状态:消费成功
return ConsumeOrderlyStatus.SUCCESS;
}
});
// 开启消费者消费
consumer.start();
System.out.println("Consumer Started");
}
}