本文基于RocketMQ 4.7.1版本
rocketmq提供了两个类用于消费消息,分别是DefaultMQPullConsumer和DefaultMQPushConsumer,下面分别介绍如何使用这两个类。
文章目录
- 一、DefaultMQPushConsumer
- 二、DefaultMQPullConsumer
一、DefaultMQPushConsumer
public static void main(String[] args) throws MQClientException {
//创建消费者,消费组为consumer-A
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("consumer-A");
//指定nameserver地址,可以有多个,使用分号分隔
consumer.setNamesrvAddr("localhost:9876");
//设置订阅的主题,第二个参数表示该消费者可以消费哪些tag的消息,tag是生产者生产消息时标记的
//*或者null表示接收所有的tag消息,可以使用“tag1||tag2”过滤消息,而且只支持中间使用||分隔
consumer.subscribe("topicTest", "*");
//设置监听器,当主题中有消息时,调用监听器消费消息
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
ConsumeConcurrentlyContext context) {
System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), msgs.get(0).getQueueId());
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
//启动消费者
consumer.start();
System.out.printf("Consumer Started.%n");
}
DefaultMQPushConsumer提供了多个重载构造函数,例如:
- public DefaultMQPushConsumer(final String consumerGroup)
- public DefaultMQPushConsumer()
- public DefaultMQPushConsumer(final String namespace, final String consumerGroup, RPCHook rpcHook)
- public DefaultMQPushConsumer(final String namespace, final String consumerGroup, RPCHook rpcHook, AllocateMessageQueueStrategy allocateMessageQueueStrategy)
- public DefaultMQPushConsumer(final String consumerGroup, RPCHook rpcHook,AllocateMessageQueueStrategy allocateMessageQueueStrategy, boolean enableMsgTrace, final String customizedTraceTopic)
上面的无参构造方法由于没有指定消费组,所以使用默认的消费组:DEFAULT_CONSUMER。不过在启动消费者的时候,rocketmq会检查组名,如果组名等于默认消费组,rocketmq会报错,所以如果使用默认构造方法创建对象后,还需要调用setConsumerGroup()方法重新设置组名。
下面介绍构造方法里面每个参数的作用。
- consumerGroup:指定消费者的消费组,每个消费者都属于一个消费组,组内的每个消费者消费主题的一个或多个队列,一个队列只能有一个消费者消费,rocketmq提供了再平衡机制保证当有多个消费者时,如果其中一个挂掉,可以将它消费的队列分配给其他消费者;
- namespace:命名空间,与订阅的主题名组合在一起,形成最终的主题名;
- rpcHook:该类提供了doBeforeRequest和doAfterResponse方法,用于在发送请求前和收到broker响应后对请求和响应内容做回调处理。
- allocateMessageQueueStrategy:再平衡时使用,用于将主题队列分配给各个消费者。
- enableMsgTrace和customizedTraceTopic:用于消息轨迹,本文不对此介绍。
我们可以根据需求选择不同的构造方法。
二、DefaultMQPullConsumer
private static final Map<MessageQueue,Long> OFFSE_TABLE = new HashMap<MessageQueue,Long>();
public static void main(String[] args) throws MQClientException {
//创建消费者
DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("consumer-A");
consumer.setNamesrvAddr("localhost:9876");
consumer.start();//启动
//获取主题下所有的消息队列
Set<MessageQueue> mqs = consumer.fetchSubscribeMessageQueues("topicTest");
for(MessageQueue mq:mqs){
try {
//获取当前队列的消费位移,第二个参数表示位移是从本地内存获取,还是从broker获取
//true表示从broker获取
long offset = consumer.fetchConsumeOffset(mq,true);
while(true){
//第二个参数表示可以消费哪些tag的消息
//第三个参数表示从哪个位移开始消费消息
//第四个参数表示一次最大拉多少个消息
PullResult pullResult = consumer.pullBlockIfNotFound(mq, null,
getMessageQueueOffset(mq), 32);
putMessageQueueOffset(mq,pullResult.getNextBeginOffset());
//根据拉取消息的状态,切换到不同分支
switch(pullResult.getPullStatus()){
case FOUND:
List<MessageExt> messageExtList = pullResult.getMsgFoundList();
//消费消息
for (MessageExt m : messageExtList) {
System.out.println(new String(m.getBody()));
}
break;
case NO_MATCHED_MSG:
break;
case NO_NEW_MSG:
break;
case OFFSET_ILLEGAL:
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
consumer.shutdown();
}
//保存下次消费消息的位移,这里将位移保存到内存,也可以使用数据库
private static void putMessageQueueOffset(MessageQueue mq,
long nextBeginOffset) {
OFFSE_TABLE.put(mq, nextBeginOffset);
}
//获取本次要消费消息的位移
private static Long getMessageQueueOffset(MessageQueue mq) {
Long offset = OFFSE_TABLE.get(mq);
if(offset != null){
return offset;
}
return 0l;
}
上面的代码遍历每个队列,将每个队列的消息消费完毕之后,切换到下一个队列消费。如果队列的消息是源源不断产生的,那么可能会导致后面的队列长时间无法消费,我将代码修改如下:
public static void main(String[] args) throws Exception{
//创建消费者
DefaultMQPullConsumer consumer = new DefaultMQPullConsumer("consumer-A");
consumer.setNamesrvAddr("localhost:9876");
consumer.start();//启动
//获取主题下所有的消息队列
Set<MessageQueue> mqs = consumer.fetchSubscribeMessageQueues("topicTest");
Object[] objs=mqs.toArray();
for (int i=0;i<objs.length;i++) {
MessageQueue mq = (MessageQueue) objs[i];
//可以用于更新本地记录的消费位移
long offset=consumer.fetchConsumeOffset(mq, true);
putMessageQueueOffset(mq, offset);
}
while(true) {
//一次只拉取一部分消息,遍历每个队列完毕后,在重新遍历一遍
//这里假设生产者不断产生消息,没有终止
for (int i=0;i<objs.length;i++) {
MessageQueue mq=(MessageQueue)objs[i];
try {
//其他代码同上
}
}
}
}
可以看到,DefaultMQPullConsumer代码相对DefaultMQPushConsumer来说,要复杂一些,我们需要手工维护消费位移。而DefaultMQPushConsumer是rocketmq自动维护的。
DefaultMQPullConsumer也提供了多个构造方法,如下:
- public DefaultMQPullConsumer(final String namespace, final String consumerGroup, RPCHook rpcHook)
- public DefaultMQPullConsumer(final String namespace, final String consumerGroup)
构造方法每个参数的作用可以参考DefaultMQPushConsumer。