本文基于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。