rocketmq顺序消费

顺序消费

什么是顺序消费?

指的是可以按照消息的发送顺序来消费。例如:一笔订单产生了 3 条消息,分别是订单创建、订单付款、订单完成。消费时,要按照顺序依次消费才有意义。与此同时多笔订单之间又是可以并行消费的。
关于消息的消费过程中,可能遇到的异常情况,可以参考该博客:https://www.jianshu.com/p/453c6e7ff81c

rocketmq是怎么实现顺序消费的呢?

①rocketmq保证同一个订单的消息,一定要发送到同一个队列
②并且该队列只有一个消费者,也就是说 同一个队列,不能出现多个消费者并行消费的情况
到这里可能有的人会问,一个队列只有一个消费者,那性能岂不是很低?关于这个情况,rocketmq的解决方法是,虽然同一个队列不能并行消费,但是可以并行消费不同的队列。就是上面说的同时多笔订单之间又是可以并行消费。
下载github上的example例子https://github.com/apache/rocketmq

顺序消费使用

查看下载的example的列子中,有个ordermessage可以拿来参考一下,然后自己写一个生产者消费者例子。
OrderProducer.java
package com.test.order;

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 java.util.List;
import java.util.concurrent.TimeUnit;

public class OrderProducer {

    public static void main(String[] args) throws MQClientException,
            InterruptedException {

        /**
         * 一个应用创建一个Producer,由应用来维护此对象,可以设置为全局对象或者单例
         * 注意:ProducerGroupName需要由应用来保证唯一
         * ProducerGroup这个概念发送普通的消息时,作用不大,但是发送分布式事务消息时,比较关键
         * 因为服务器会回查这个Group下的任意一个Producer
         */
        DefaultMQProducer producer = new DefaultMQProducer("ProducerGroupName");
        producer.setNamesrvAddr("172.20.1.186:9876");
        producer.setInstanceName("Producer");
        /**
         * Rocket默认开启了VIP通道,VIP通道端口为10911-2=10909。若Rocket服务器未启动端口10909,则报connect to <> failed
         */
        producer.setVipChannelEnabled(false);

        /**
         * Producer对象在使用之前必须要调用start初始化,初始化一次即可
         * 注意:切记不可以在每次发送消息时,都调用start方法
         */
        producer.start();

        /**
         * 下面这段代码表明一个Producer对象可以发送多个topic,多个tag的消息。
         * 注意:send方法是同步调用,只要不抛异常就标识成功。但是发送成功也可会有多种状态,
         * 例如消息写入Master成功,但是Slave不成功,这种情况消息属于成功,但是对于个别应用如果对消息可靠性要求极高
         * 需要对这种情况做处理。另外,消息可能会存在发送失败的情况,失败重试由应用来处理
         */
        for (int i = 1; i <= 10; i++) {
            try {

                /**
                 * TopicTest要发送的队列
                 * TagA标签,可以达到再次过滤,消费端可以只消费TagA的消息类似于这样
                 * key可以在控制台根据key查询消息
                 * body消息体内容
                 */
                Message msg = new Message("TopicTest",// topic
                        "TagA",// tag
                        "key1"+i,// key
                        ("订单一号" + i).getBytes());// body
                SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
                    @Override
                    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                        //这里的arg就是orderId传进来的
                        Integer id=(Integer) arg;
                        //取模决定放在哪个数据库
                        int index = id % mqs.size();
                        return mqs.get(index);
                    }
                },1);//订单号为1
                System.out.println(sendResult);


            } catch (Exception e) {
                e.printStackTrace();
            }
            TimeUnit.MILLISECONDS.sleep(1000);
        }
        for (int i = 1; i <= 10; i++) {
            try {
                int orderId = i;
                Message msg = new Message("TopicTest",// topic
                        "TagA",// tag
                        "key2"+i,// key
                        ("订单二号" + i).getBytes());// body
                SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
                    @Override
                    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                        //这里的arg就是orderId传进来的
                        Integer id=(Integer) arg;
                        //取模决定放在哪个数据库
                        int index = id % mqs.size();
                        return mqs.get(index);
                    }
                },2);//订单号为2
                System.out.println(sendResult);


            } catch (Exception e) {
                e.printStackTrace();
            }
            TimeUnit.MILLISECONDS.sleep(1000);
        }
        for (int i = 1; i <= 10; i++) {
            try {
                int orderId = i;
                Message msg = new Message("TopicTest",// topic
                        "TagA",// tag
                        "key3"+i,// key
                        ("订单三号" + i).getBytes());// body
                SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
                    @Override
                    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                        //这里的arg就是orderId传进来的
                        Integer id=(Integer) arg;
                        //取模决定放在哪个数据库
                        int index = id % mqs.size();
                        return mqs.get(index);
                    }
                },3);//订单号为3
                System.out.println(sendResult);

            } catch (Exception e) {
                e.printStackTrace();
            }
            TimeUnit.MILLISECONDS.sleep(1000);
        }


        /**
         * 应用退出时,要调用shutdown来清理资源,关闭网络连接,从MetaQ服务器上注销自己
         * 注意:我们建议应用在JBOSS、Tomcat等容器的退出钩子里调用shutdown方法
         */
        producer.shutdown();
    }

}

在获取到路由信息以后,会根据MessageQueueSelector实现的算法来选择一个队列,同一个OrderId获取到的肯定是同一个队列,这里的OrderIdId就是对应的不同的订单号

OrderConsumer.java
package com.test.order;

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 org.apache.rocketmq.common.protocol.heartbeat.MessageModel;

import java.util.List;

public class OrderConsumer {

    public static void main(String[] args) throws InterruptedException,
            MQClientException {

        /**
         * 一个应用创建一个Consumer,由应用来维护此对象,可以设置为全局对象或者单例
         * 注意:ConsumerGroupName需要由应用来保证唯一
         */
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ConsumerGroupName");
        consumer.setNamesrvAddr("172.20.1.186:9876");
        consumer.setInstanceName("Consumer1");
        consumer.setMessageModel(MessageModel.CLUSTERING);

        //消费线程最小数量
        //consumer.setConsumeThreadMin(30);
        //消费线程最大数量
        //consumer.setConsumeThreadMax(100);


        /**
         * 订阅指定topic下tags分别等于TagA或TagC或TagD, 这里没有订阅TagB的消息,所以不会消费标签为TagB的消息,*代表不过滤 接受一切
         */
        //consumer.subscribe("TopicTest", "TagA || TagC || TagD");
        consumer.subscribe("TopicTest", "*");

        /**
         * 如果是顺序消息,这边的监听就要使用MessageListenerOrderly监听
         * 并且,返回结果也要使用ConsumeOrderlyStatus
         */
        consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                //设置自动提交,如果不设置自动提交就算返回SUCCESS,消费者关闭重启 还是会重复消费的
                context.setAutoCommit(true);
                try {
                    for (MessageExt msg:msgs) {
                        System.out.println(" 消费者1 ==> 当前线程:"+Thread.currentThread().getName()+" ,quenuID: "+msg.getQueueId()+ " ,content: " + new String(msg.getBody()));
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                    //如果出现异常,消费失败,挂起消费队列一会会,稍后继续消费
                    return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
                }

                //消费成功
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

        /**
         * Consumer对象在使用之前必须要调用start初始化,初始化一次即可
         */
        consumer.start();

        System.out.println("C1 Started.");
    }
}

注意:这里的Consumer使用的是MessageListenerOrderly类,不再使用MessageListenerConcurrently类。返回的状态也不再是ConsumeConcurrentlyStatus而是ConsumeOrderlyStatus

测试

先启动Consumer端订阅,然后发布消息,查看打印日志


可以看到消费的是有顺序的,并且rocketmq默认就有20个线程在消费,我们也可以来修改这个默认的线程数量,通过以下两个参数
//消费线程最小数量,默认20
consumer.setConsumeThreadMin(30);
//消费线程最大数量,默认64
consumer.setConsumeThreadMax(100);
再次测试,负载均衡是否支持消息顺序执行
将上面的OrderConsumer.java再次复制一份,日志添加  "消费者1"  "消费者2" 用来区分 不同的消费者
启动C1,C2查看控制台消费者状况
消费者1日志如下:

消费者2日志如下:

可以看到负载均衡,并且消息是有顺序的。同一个订单的消息都会放到同一个队列中。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值