rocketMQ学习笔记(二) 顺序消费

一、应用场景

顺序消费就是先生产的消息先消费,即生产者依次生产了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复制一份出来即可



阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页