一、应用场景
顺序消费就是先生产的消息先消费,即生产者依次生产了1,2,3这三条消息,消费者也要安装1,2,3这样的顺序来消费。
适用于消息队列中消息之间有先后的依赖关系,后一条消息的处理依赖于前一条消息的处理结果。
二、实现方式
RocketMQ可以保证顺序消费,他的实现是生产者(一个生产者可以对多个主题去发送消息)将这个三个消息放在topic(一个topic默认有4个队列)的一个队列里面,单机支持上万个持久化队列,消费端去消费的时候也是只能有一个Consumer去取得这个队列里面的数据,然后顺序消费。
单个节点(Producer端1个、Consumer端1个)
Producer:
package com.huaguoguo.example.rocketmq.order.local;
import org.apache.rocketmq.client.exception.MQClientException;
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;
/**
* @Author:huaguoguo
* @Description:局部有序消息-生产者
* @Date: 2018/4/27 15:18
*/
public class Producer {
public static void main(String[] args) throws MQClientException, InterruptedException {
/**
* 一个应用创建一个Producer,由应用来维护此对象,可以设置为全局对象或者单例
* 注意:ProducerGroupName需要由应用来保证唯一
* ProducerGroup这个概念发送普通的消息时,作用不大,但是发送分布式事务消息时,比较关键,
* 因为服务器会回查这个Group下的任意一个Producer
*/
DefaultMQProducer producer = new DefaultMQProducer("order_Producer");
producer.setNamesrvAddr("xxx:9876");
/**
* Producer对象在使用之前必须要调用start初始化,初始化一次即可
* 注意:切记不可以在每次发送消息时,都调用start方法
*/
producer.start();
/**
* 下面这段代码表明一个Producer对象可以发送多个topic,多个tag的消息。
* 注意:send方法是同步调用,只要不抛异常就标识成功。但是发送成功也可会有多种状态,
* 例如消息写入Master成功,但是Slave不成功,这种情况消息属于成功,但是对于个别应用如果对消息可靠性要求极高
* 需要对这种情况做处理。另外,消息可能会存在发送失败的情况,失败重试由应用来处理。
*/
for (int i = 0; i < 111; i++) {
try {
/*
* 创建一个消息对象
*/
Message msg = new Message("TopicOrderTest" /* Topic */,
"order_1" /* Tag */,
"KEY" + i,/* key */
("顺序消息order_1_"+i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
/*
* 调用producer发送消息来将消息传递给brokers。
*/
SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
Integer id = (Integer) arg;
int index = id % mqs.size();
return mqs.get(index);
}
},0);
System.out.printf("%s%n", sendResult);
} catch (Exception e) {
e.printStackTrace();
Thread.sleep(1000);
}
}
/**
* 应用退出时,要调用shutdown来清理资源,关闭网络连接,从MetaQ服务器上注销自己
* 注意:我们建议应用在JBOSS、Tomcat等容器的退出钩子里调用shutdown方法
*/
producer.shutdown();
}
}
Consumer:
package com.huaguoguo.example.rocketmq.order.local;
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.message.MessageExt;
import java.util.List;
public class Consumer {
/**
* 当前例子是PushConsumer用法,使用方式给用户感觉是消息从RocketMQ服务器推到了应用客户端。<br>
* 但是实际PushConsumer内部是使用长轮询Pull方式从MetaQ服务器拉消息,然后再回调用户Listener方法<br>
*/
public static void main(String[] args) throws InterruptedException, MQClientException {
/**
* 一个应用创建一个Consumer,由应用来维护此对象,可以设置为全局对象或者单例
* 注意:ConsumerGroupName需要由应用来保证唯一
*/
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("order_Consumer");
consumer.setNamesrvAddr("xxx:9876");
/*
* Specify where to start in case the specified consumer group is a brand new one.
*/
// consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
/**
* 订阅指定topic下所有消息
* 注意:一个consumer对象可以订阅多个topic
*/
consumer.subscribe("TopicOrderTest", "*");
/*
* 注册回调,以便在从brokers那里获得的消息到达时执行。
*/
consumer.registerMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
for (MessageExt msg : msgs) {
System.out.printf(Thread.currentThread().getName() + " Receive New Messages: " + new String(msg.getBody()) + "%n");
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
/*
* Consumer对象在使用之前必须要调用start初始化,初始化一次即可
*/
consumer.start();
System.out.printf("Consumer Started.%n");
}
}
运行消费者后的控制台打印:
可以看到消息被顺序消费了,发现一个现象,每31条消息在一个线程中消费,先留着以后再看。
多个节点(Producer端1个、Consumer端2个)与上面代码一样,把Consumer复制一份出来即可