文章目录
- 顺序消费的原理解析
- 示例
- 1. 创建订单类
- 2. 创建 Producer
- 2.1 在 Producer 类中创建构建订单的方法
- 2.2 在 Producer 类中实现发送消息
- 3. 消费者实现
顺序消息指的是可以按照消息的发送顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序,可以分为分区有序或者全局有序。
顺序消费的原理解析
在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列);而消费消息的时候从多个queue上拉取消息,这种情况发送和消费是不能保证顺序。如下图所示:
但是如果控制发送的顺序消息只依次发送到同一个queue中,消费的时候只从这个queue上依次拉取,则就保证了顺序。如下图所示:
当发送和消费参与的queue只有一个,则是全局有序;如果多个queue参与,则为分区有序,即相对每个queue,消息都是有序的。
示例
下面用订单进行分区有序的示例。一个订单的顺序流程是:创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中,消费时,同一个OrderId获取到的肯定是同一个队列。
1. 创建订单类
public class OrderStep {
private long orderId;
private String describe;
public long getOrderId() {
return orderId;
}
public void setOrderId(long orderId) {
this.orderId = orderId;
}
public String getDescribe() {
return describe;
}
public void setDescribe(String describe) {
this.describe = describe;
}
@Override
public String toString() {
return "OrderStep{" +
"orderId=" + orderId +
", describe='" + describe + '\'' +
'}';
}
}
2. 创建 Producer
2.1 在 Producer 类中创建构建订单的方法
/**
* 创建三个订单。订单编号为:100001、100002、100003
* 每个订单都有四个步骤:创建、付款、推送、完成
* @return 订单集合
*/
private static List<OrderStep> buildOrders(){
List<OrderStep> orderList = new ArrayList<>();
OrderStep orderDemo = new OrderStep();
orderDemo.setOrderId(100001L);
orderDemo.setDescribe("创建");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(100002L);
orderDemo.setDescribe("创建");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(100001L);
orderDemo.setDescribe("付款");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(100003L);
orderDemo.setDescribe("创建");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(100002L);
orderDemo.setDescribe("付款");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(100003L);
orderDemo.setDescribe("付款");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(100002L);
orderDemo.setDescribe("推送");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(100002L);
orderDemo.setDescribe("完成");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(100003L);
orderDemo.setDescribe("推送");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(100001L);
orderDemo.setDescribe("推送");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(100003L);
orderDemo.setDescribe("完成");
orderList.add(orderDemo);
orderDemo = new OrderStep();
orderDemo.setOrderId(100001L);
orderDemo.setDescribe("完成");
orderList.add(orderDemo);
return orderList;
}
可以看到在创建订单集合的时候,并不是把每个订单创建完成后再创建下一个订单。而是多个订单创建流程穿插在一起的。
但是每个订单的四个步骤(创建、付款、推送、完成)确实依次创建
这样做的目的是为了印证不同的订单ID,会存放在不同的队列中。
2.2 在 Producer 类中实现发送消息
public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
// 创建 producer 实例,并设置生产者组名
DefaultMQProducer producer = new DefaultMQProducer("orderGroup");
// 设置 NameServer 地址
producer.setNamesrvAddr("127.0.0.1:9876");
// 启动生产者
producer.start();
// 构建订单步骤列表
List<OrderStep> orderList = OrderProducer.buildOrders();
// 一共12个订单,这里循环12次
for (int i = 0; i < 12; i++) {
// 消息 body
String body =" Hello RocketMQ " + orderList.get(i);
// 创建消息并制定 topic、tag和消息内容
Message msg = new Message("orderTopic", "orderTag", body.getBytes());
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
/**
* @param mqs 队列集合
* @param msg 消息对象
* @param arg 业务标识
*/
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
Long id = (Long) arg; //根据订单id选择发送queue
long index = id % mqs.size();
return mqs.get((int) index);
}
}, orderList.get(i).getOrderId());//订单id
System.out.println(String.format("SendResult status:%s, queueId:%d, body:%s",
sendResult.getSendStatus(),
sendResult.getMessageQueue().getQueueId(),
body));
}
// 发送完成就关闭 producer
producer.shutdown();
}
打印结果:
SendResult status:SEND_OK, queueId:1, body: Hello RocketMQ OrderStep{orderId=100001, describe='创建'}
SendResult status:SEND_OK, queueId:2, body: Hello RocketMQ OrderStep{orderId=100002, describe='创建'}
SendResult status:SEND_OK, queueId:1, body: Hello RocketMQ OrderStep{orderId=100001, describe='付款'}
SendResult status:SEND_OK, queueId:3, body: Hello RocketMQ OrderStep{orderId=100003, describe='创建'}
SendResult status:SEND_OK, queueId:2, body: Hello RocketMQ OrderStep{orderId=100002, describe='付款'}
SendResult status:SEND_OK, queueId:3, body: Hello RocketMQ OrderStep{orderId=100003, describe='付款'}
SendResult status:SEND_OK, queueId:2, body: Hello RocketMQ OrderStep{orderId=100002, describe='推送'}
SendResult status:SEND_OK, queueId:2, body: Hello RocketMQ OrderStep{orderId=100002, describe='完成'}
SendResult status:SEND_OK, queueId:3, body: Hello RocketMQ OrderStep{orderId=100003, describe='推送'}
SendResult status:SEND_OK, queueId:1, body: Hello RocketMQ OrderStep{orderId=100001, describe='推送'}
SendResult status:SEND_OK, queueId:3, body: Hello RocketMQ OrderStep{orderId=100003, describe='完成'}
SendResult status:SEND_OK, queueId:1, body: Hello RocketMQ OrderStep{orderId=100001, describe='完成'}
可以看见一个细节:订单ID相同的数据,发送到了相同的队列中。
3. 消费者实现
public static void main(String[] args) throws MQClientException {
// 创建 consumer 实例,并设置生产者组名
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("orderGroup");
// 设置 NameServer 地址
consumer.setNamesrvAddr("127.0.0.1:9876");
/*
* 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费<br>
* 如果非第一次启动,那么按照上次消费的位置继续消费
*/
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
// 设置 topic和tag
consumer.subscribe("orderTopic", "orderTag");
// 注册消息监听器
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
context.setAutoCommit(true);
for (MessageExt msg : msgs) {
// 可以看到每个queue有唯一的consume线程来消费, 订单对每个queue(分区)有序
System.out.println("consumeThread=" + Thread.currentThread().getName() + ", queueId=" + msg.getQueueId() + ", content:" + new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
consumer.start();
System.out.println("Consumer Started.");
}
打印结果:
consumeThread=ConsumeMessageThread_4, queueId=1, content: Hello RocketMQ OrderStep{orderId=100001, describe='创建'}
consumeThread=ConsumeMessageThread_5, queueId=2, content: Hello RocketMQ OrderStep{orderId=100002, describe='创建'}
consumeThread=ConsumeMessageThread_6, queueId=1, content: Hello RocketMQ OrderStep{orderId=100001, describe='付款'}
consumeThread=ConsumeMessageThread_7, queueId=3, content: Hello RocketMQ OrderStep{orderId=100003, describe='创建'}
consumeThread=ConsumeMessageThread_8, queueId=2, content: Hello RocketMQ OrderStep{orderId=100002, describe='付款'}
consumeThread=ConsumeMessageThread_9, queueId=3, content: Hello RocketMQ OrderStep{orderId=100003, describe='付款'}
consumeThread=ConsumeMessageThread_10, queueId=2, content: Hello RocketMQ OrderStep{orderId=100002, describe='推送'}
consumeThread=ConsumeMessageThread_11, queueId=2, content: Hello RocketMQ OrderStep{orderId=100002, describe='完成'}
consumeThread=ConsumeMessageThread_12, queueId=3, content: Hello RocketMQ OrderStep{orderId=100003, describe='推送'}
consumeThread=ConsumeMessageThread_13, queueId=1, content: Hello RocketMQ OrderStep{orderId=100001, describe='推送'}
consumeThread=ConsumeMessageThread_14, queueId=3, content: Hello RocketMQ OrderStep{orderId=100003, describe='完成'}
consumeThread=ConsumeMessageThread_15, queueId=1, content: Hello RocketMQ OrderStep{orderId=100001, describe='完成'}
可以看到每个 queue 有唯一的 consumer 线程来消费, 订单对每个queue(分区)有序
技 术 无 他, 唯 有 熟 尔。
知 其 然, 也 知 其 所 以 然。
踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。