RocketMQ 顺序消费只消费一次 坑

本文详细解析了RocketMQ实现顺序消息消费的原理,通过将消息发送到同一队列并使用MessageListenerOrderly确保单线程消费,从而保障消息的顺序处理。文章提供了Producer和Consumer的代码示例,强调了监听类的正确实现方式。

rocketMq实现顺序消费的原理

produce在发送消息的时候,把消息发到同一个队列(queue)中,消费者注册消息监听器为MessageListenerOrderly,这样就可以保证消费端只有一个线程去消费消息

注意:是把把消息发到同一个队列(queue),不是同一个topic,默认情况下一个topic包括4个queue

单个节点(Producer端1个、Consumer端1个)

1、Producer.java 

 

复制代码

package order;  
  
import java.util.List;  
  
import com.alibaba.rocketmq.client.exception.MQBrokerException;  
import com.alibaba.rocketmq.client.exception.MQClientException;  
import com.alibaba.rocketmq.client.producer.DefaultMQProducer;  
import com.alibaba.rocketmq.client.producer.MessageQueueSelector;  
import com.alibaba.rocketmq.client.producer.SendResult;  
import com.alibaba.rocketmq.common.message.Message;  
import com.alibaba.rocketmq.common.message.MessageQueue;  
import com.alibaba.rocketmq.remoting.exception.RemotingException;  
  
/** 
 * Producer,发送顺序消息 
 */  
public class Producer {  
    public static void main(String[] args) {  
        try {  
            DefaultMQProducer producer = new DefaultMQProducer("order_Producer");  
            producer.setNamesrvAddr("192.168.100.145:9876;192.168.100.146:9876;192.168.100.149:9876;192.168.100.239:9876");  
  
            producer.start();  
  
            // String[] tags = new String[] { "TagA", "TagB", "TagC", "TagD",  
            // "TagE" };  
  
            for (int i = 1; i <= 5; i++) {  
  
                Message msg = new Message("TopicOrderTest", "order_1", "KEY" + i, ("order_1 " + i).getBytes());  
  
                SendResult sendResult = producer.send(msg, new MessageQueueSelector() {  
                    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.println(sendResult);  
            }  
  
            producer.shutdown();  
        } catch (MQClientException e) {  
            e.printStackTrace();  
        } catch (RemotingException e) {  
            e.printStackTrace();  
        } catch (MQBrokerException e) {  
            e.printStackTrace();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}

复制代码

2、Consumer.java   (有问题)

 

复制代码

package order;   
import java.util.List;  
import java.util.concurrent.TimeUnit;  
import java.util.concurrent.atomic.AtomicLong;   
import com.alibaba.rocketmq.client.consumer.DefaultMQPushConsumer;  
import com.alibaba.rocketmq.client.consumer.listener.ConsumeOrderlyContext;  
import com.alibaba.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;  
import com.alibaba.rocketmq.client.consumer.listener.MessageListenerOrderly;  
import com.alibaba.rocketmq.client.exception.MQClientException;  
import com.alibaba.rocketmq.common.consumer.ConsumeFromWhere;  
import com.alibaba.rocketmq.common.message.MessageExt;  
  
/** 
 * 顺序消息消费,带事务方式(应用可控制Offset什么时候提交) 
 */  
public class Consumer1 {  
  
    public static void main(String[] args) throws MQClientException {  
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("order_Consumer");  
        consumer.setNamesrvAddr("192.168.100.145:9876;192.168.100.146:9876;192.168.100.149:9876;192.168.100.239:9876");  
  
        /** 
         * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费<br> 
         * 如果非第一次启动,那么按照上次消费的位置继续消费 
         */  
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);  
  
        consumer.subscribe("TopicOrderTest", "*");  
  
        consumer.registerMessageListener(new MessageListenerOrderly() {  
            AtomicLong consumeTimes = new AtomicLong(0);  
  
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {  
                // 设置自动提交  
                context.setAutoCommit(true);  
                for (MessageExt msg : msgs) {  
                    System.out.println(msg + ",内容:" + new String(msg.getBody()));  
                }  
  
                try {  
                    TimeUnit.SECONDS.sleep(5L);  
                } catch (InterruptedException e) {  
  
                    e.printStackTrace();  
                }  
                ;  
  
                return ConsumeOrderlyStatus.SUCCESS;  
            }  
        });  
  
        consumer.start();  
  
        System.out.println("Consumer1 Started.");  
    }  
  
}

复制代码

 

 

这个地方有一个大坑,注册监听类的时候,不能使用匿名内部类。不然的话,只会消费一次,然后消费者就 挂了……挂了……挂了…… 

监听类要单独写!!!

 

正确消费者写法:

自定义监听类:

MyMessageListener

按 Ctrl+C 复制代码

 

按 Ctrl+C 复制代码

Consumer.java   

复制代码

public class Consumer {

    public static void main(String[] args) throws MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("order_Consumer");
        consumer.setNamesrvAddr("101.200.33.225:9876");

        /**
         * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费<br>
         * 如果非第一次启动,那么按照上次消费的位置继续消费
         */
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

        consumer.subscribe("TopicOrderTest", "*");

        MyMessageListener myMessageListener = new MyMessageListener();

        consumer.registerMessageListener(myMessageListener);
        
        consumer.start();

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

复制代码

 

参考:https://www.cnblogs.com/antain/p/rocketmq.html

           http://www.cnblogs.com/520playboy/p/6750023.html

           http://dbaplus.cn/news-21-1123-1.html

 

转载于:https://www.cnblogs.com/Jtianlin/p/8436024.html

 

### RocketMQ 实现顺序消费的方法及原理 RocketMQ 提供了一种高效的顺序消息机制,能够确保消息按照生产者发送的顺序消费消费。以下是其实现方法及其背后的原理: #### 1. 消息队列分区设计 在 RocketMQ 中,主题(Topic)会被划分为多个队列(Queue),每个队列一个独立的消息存储单元。对于顺序消息而言,同一业务逻辑下的消息会严格写入到同一个队列中[^1]。这样做的目的是为了保证单个队列内的消息具有严格的先后次序。 #### 2. 单线程模型处理顺序消息 当消费者订阅某个 Topic 并开启顺序消费模式时,RocketMQ 使用 `DefaultMQPushConsumer` 或其内部实现类 `DefaultMQPushConsumerImpl` 进行管理[^2]。具体来说,在顺序消费场景下,RocketMQ 将为每一个 Message Queue 配置单独的消费线程。这种一对一的关系确保了即使存在多条消息并发到达的情况,同一条队列中的消息仍然由固定的线程按顺序处理。 #### 3. Spring 集成支持 如果通过 Spring Boot 或其他框架集成 RocketMQ,则可以通过 `DefaultRocketMQListenerContainer` 类进一步简化配置过程[^3]。该容器负责监听指定的主题和标签组合,并自动分配合适的 Consumer 资源用于执行回调函数。值得注意的是,默认情况下它也遵循上述提到的一对一线程绑定策略以保障全局一致性。 #### 4. 多实例部署优化 尽管单线程模型可以很好地解决局部范围内的序列化需求,但在大规模分布式环境下可能会成为性能瓶颈。因此 RocketMQ 支持跨机器间的负载均衡以及分组隔离功能——即允许不同消费者的子集分别读取部分数据流而不互相干扰;同时每台服务器上的本地操作依旧维持原有的串行语义不变。 ```java // 示例代码展示如何设置顺序消费者 consumer.subscribe("OrderTopic", "*", new MessageListenerOrderly() { @Override public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) { try { // 对列表里的所有记录逐一解析并应用业务逻辑... System.out.printf(Thread.currentThread().getName()+" Receive New Messages: " + msgs); return ConsumeOrderlyStatus.SUCCESS; } catch (Exception e){ return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; } } }); ``` 以上就是关于 RocketMQ 如何达成高效可靠地顺序传递的核心要点概述。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值