RocketMQ的顺序消费实现(分区有序、全局有序)

文章介绍了如何在RocketMQ中实现全局和分区有序的消息传递。全局有序通过单个队列和单个生产者消费者实现,而分区有序则利用自定义消息队列选择策略和消费者负载均衡,确保同一订单的消息按序消费。代码示例展示了生产者如何根据订单ID选择发送队列,以及消费者如何进行有序消费。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

顺序消息

1 全局有序

全局有序比较简单,主要控制在于创建Topic指定只有一个队列,同步确保生产者与消费者都只有一个实例进行即可。

在这里插入图片描述

2 分区有序

在电商业务场景中,一个订单的流程是:创建、付款、推送、完成。在加入RocketMQ后,一个订单会分别产生对于这个订单的创建、付款、推送、完成等消息,如果我们把所有消息全部送入到RocketMQ中的一个主题中,这里该如何实现针对一个订单的消息顺序性呢!如下图:

在这里插入图片描述

要完成分区有序性,在生产者环节使用自定义的消息队列选择策略,确保订单号尾数相同的消息会被先后发送到同一个队列中(案例中主题有3个队列,生产环境中可设定成10个满足全部尾数的需求),然后再消费端开启负载均衡模式,最终确保一个消费者拿到的消息对于一个订单来说是有序的。

代码实现生产者

package com.neu.rocketmq.example.ordermessage;

import lombok.Data;
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.ArrayList;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: yqq
 * @Date: 2023/06/19/21:43
 * @Description:
 */
public class ProducerInOrder {

    @Data
    private static class Order{
        private long orderId;
        private String desc;
    }

    private List<Order> buildOrders(){
        List<Order> orderList = new ArrayList<>();
        Order order = new Order();
        //订单1
        order.setOrderId(001);
        order.setDesc("创建");
        orderList.add(order);

        order = new Order();
        order.setOrderId(001);
        order.setDesc("付款");
        orderList.add(order);

        order = new Order();
        order.setOrderId(001);
        order.setDesc("推送");
        orderList.add(order);

        order = new Order();
        order.setOrderId(001);
        order.setDesc("完成");
        orderList.add(order);

        //订单2
        order = new Order();
        order.setOrderId(002);
        order.setDesc("创建");
        orderList.add(order);

        order = new Order();
        order.setOrderId(002);
        order.setDesc("付款");
        orderList.add(order);

        order = new Order();
        order.setOrderId(002);
        order.setDesc("推送");
        orderList.add(order);

        order = new Order();
        order.setOrderId(002);
        order.setDesc("完成");
        orderList.add(order);

        //订单3
        order = new Order();
        order.setOrderId(003);
        order.setDesc("创建");
        orderList.add(order);

        order = new Order();
        order.setOrderId(003);
        order.setDesc("付款");
        orderList.add(order);

        order = new Order();
        order.setOrderId(003);
        order.setDesc("推送");
        orderList.add(order);

        order = new Order();
        order.setOrderId(003);
        order.setDesc("完成");
        orderList.add(order);

        return orderList;
    }

    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("OrderProducer00");
        producer.setNamesrvAddr("node1:9876");
        producer.start();
        //订单列表
        List<Order> orderList = new ProducerInOrder().buildOrders();
        for (int i = 0; i < orderList.size(); i++) {
            String body = orderList.get(i).toString();
            Message msg = new Message("PartOrder010", null, "KEY" + i, body.getBytes());
            SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
                @Override
                public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                    Long id = (Long) arg;//根据订单ID选择发送queue
                    long index = id % mqs.size();
                    return mqs.get((int) index);
                }
            },orderList.get(i).getOrderId());//订单ID
            System.out.println(String.format("SendResult status:%s, queueId:%d, body:%s",
                    sendResult.getSendStatus(),
                    sendResult.getMessageQueue().getQueueId(),
                    body
            ));
        }
        producer.shutdown();
    }
}

在这里插入图片描述

代码实现消费者

package com.neu.rocketmq.example.ordermessage;

import jdk.nashorn.internal.ir.CallNode;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: yqq
 * @Date: 2023/06/19/21:44
 * @Description:
 */
public class ConsumerInOrder {
    public static void main(String[] args) throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("OrderConsumer2");
        consumer.setNamesrvAddr("node1:9876");
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
        consumer.subscribe("PartOrder010","*");
        consumer.registerMessageListener(new MessageListenerOrderly() {
            Random random = new Random();
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                context.setAutoCommit(true);
                for (MessageExt msg : msgs){
                    //每个queue有唯一的consume线程来消费,订单对每个queue分区有序
                    System.out.println("consumeThread=" + Thread.currentThread().getName() + ", " +
                            "queueId" + msg.getQueueId() + ", content:" + new String(msg.getBody()));
                }
                try {
                    //模拟处理业务逻辑
                    TimeUnit.MILLISECONDS.sleep(300);
                } catch (Exception e) {
                    e.printStackTrace();
                    //先等一会儿,再处理这批消息,而不是放到重试队列里
                    return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });
        consumer.start();
        System.out.println("Consumer started.%s");
    }
}

在这里插入图片描述

注意事项

使用顺序消息:首先要保证消息是有序进入MQ的,消息放入MQ之前,对id等关键字进行取模,放入指定messageQueue,同时consume消费消息失败时,不能返回reconsume——later,这样会导致乱序,所以应该返回suspend_current_queue_a_moment,意思是先等一会,一会儿再处理这批消息,而不是放到重试队列里。

### RocketMQ实现消息顺序消费的方法 #### 机制解析 RocketMQ 提供了两种主要的方式用于保障消息的顺序消费,即分区顺序消息(Partition Ordered Messages)和全局顺序消息(Global Ordered Messages),两者均能确保消息按照发送时的先后次序被消费者处理[^3]。 对于 **分区顺序消息** 而言,在同一主题下的不同队列之间可以并行消费;然而针对单个队列内的消息,则采用串行化的方式来保证该队列内消息的严格有序性。这种方式既提高了系统的吞吐量又满足了一定程度上的业务需求——当只需要部分数据保持相对顺序即可的情况下尤为适用。 至于 **全局顺序消息**, 则更为严格地控制着整个Topic下所有Message都必须依照生产者写入Broker的时间戳来进行逐一读取操作, 这种模式适用于那些对整体流程有着极高同步度要求的应用场景之中. 由于为了实现顺序消费引入了三把锁,这确实会在一定程度上降低并发性能。因此破局思路在于重新审视这些锁定策略以及探索其他可能的技术手段来缓解这一矛盾,比如通过调整业务逻辑减少对绝对顺序性的依赖或是优化内部算法提高效率等措施[^1]。 #### 配置方法 要启用RocketMQ中的顺序消费特性,开发者可以在创建`DefaultMQPushConsumer`实例之后设置相应的监听器类: ```java consumer.subscribe("YourTopicName", "*", new MessageListenerOrderly() { AtomicLong consumeTimes = new AtomicLong(0); @Override public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) { try { // Process messages here. return ConsumeOrderlyStatus.SUCCESS; } catch (Exception e) { return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; } } }); ``` 上述代码片段展示了如何注册一个实现了 `MessageListenerOrderly` 接口的对象作为回调函数,每当有新一批待处理的消息到达时就会触发此接口里的 `consumeMessage()` 方法执行具体的业务逻辑[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

留不住的人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值