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

顺序消息

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,意思是先等一会,一会儿再处理这批消息,而不是放到重试队列里。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

留不住的人

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

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

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

打赏作者

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

抵扣说明:

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

余额充值