文章目录

  • 顺序消费的原理解析
  • 示例
  • 1. 创建订单类
  • 2. 创建 Producer
  • 2.1 在 Producer 类中创建构建订单的方法
  • 2.2 在 Producer 类中实现发送消息
  • 3. 消费者实现

顺序消息指的是可以按照消息的发送顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序,可以分为分区有序或者全局有序。

顺序消费的原理解析

在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列);而消费消息的时候从多个queue上拉取消息,这种情况发送和消费是不能保证顺序。如下图所示:

spring boot Rocketmq 消费顺序消息 rocketmq顺序写_ide

但是如果控制发送的顺序消息只依次发送到同一个queue中,消费的时候只从这个queue上依次拉取,则就保证了顺序。如下图所示:

spring boot Rocketmq 消费顺序消息 rocketmq顺序写_顺序消息_02

当发送和消费参与的queue只有一个,则是全局有序;如果多个queue参与,则为分区有序,即相对每个queue,消息都是有序的。

示例

下面用订单进行分区有序的示例。一个订单的顺序流程是:创建、付款、推送、完成。订单号相同的消息会被先后发送到同一个队列中,消费时,同一个OrderId获取到的肯定是同一个队列。

1. 创建订单类

public class OrderStep {

    private long orderId;
    private String describe;

    public long getOrderId() {
        return orderId;
    }

    public void setOrderId(long orderId) {
        this.orderId = orderId;
    }

    public String getDescribe() {
        return describe;
    }

    public void setDescribe(String describe) {
        this.describe = describe;
    }

    @Override
    public String toString() {
        return "OrderStep{" +
                "orderId=" + orderId +
                ", describe='" + describe + '\'' +
                '}';
    }
}

2. 创建 Producer

2.1 在 Producer 类中创建构建订单的方法

/**
     * 创建三个订单。订单编号为:100001、100002、100003
     * 每个订单都有四个步骤:创建、付款、推送、完成
     * @return 订单集合
     */
    private static List<OrderStep> buildOrders(){
        List<OrderStep> orderList = new ArrayList<>();

        OrderStep orderDemo = new OrderStep();
        orderDemo.setOrderId(100001L);
        orderDemo.setDescribe("创建");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(100002L);
        orderDemo.setDescribe("创建");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(100001L);
        orderDemo.setDescribe("付款");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(100003L);
        orderDemo.setDescribe("创建");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(100002L);
        orderDemo.setDescribe("付款");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(100003L);
        orderDemo.setDescribe("付款");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(100002L);
        orderDemo.setDescribe("推送");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(100002L);
        orderDemo.setDescribe("完成");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(100003L);
        orderDemo.setDescribe("推送");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(100001L);
        orderDemo.setDescribe("推送");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(100003L);
        orderDemo.setDescribe("完成");
        orderList.add(orderDemo);

        orderDemo = new OrderStep();
        orderDemo.setOrderId(100001L);
        orderDemo.setDescribe("完成");
        orderList.add(orderDemo);

        return orderList;
    }

可以看到在创建订单集合的时候,并不是把每个订单创建完成后再创建下一个订单。而是多个订单创建流程穿插在一起的。

但是每个订单的四个步骤(创建、付款、推送、完成)确实依次创建
这样做的目的是为了印证不同的订单ID,会存放在不同的队列中。

2.2 在 Producer 类中实现发送消息

public static void main(String[] args) throws MQClientException, RemotingException, InterruptedException, MQBrokerException {
        // 创建 producer 实例,并设置生产者组名
        DefaultMQProducer producer = new DefaultMQProducer("orderGroup");
        // 设置 NameServer 地址
        producer.setNamesrvAddr("127.0.0.1:9876");
        // 启动生产者
        producer.start();

        // 构建订单步骤列表
        List<OrderStep> orderList = OrderProducer.buildOrders();

        // 一共12个订单,这里循环12次
        for (int i = 0; i < 12; i++) {
            // 消息 body
            String body =" Hello RocketMQ " + orderList.get(i);
            // 创建消息并制定 topic、tag和消息内容
            Message msg = new Message("orderTopic", "orderTag", body.getBytes());

            SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
                /**
                 * @param mqs 队列集合
                 * @param msg 消息对象
                 * @param arg 业务标识
                 */
                @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
        producer.shutdown();
    }

打印结果:

SendResult status:SEND_OK, queueId:1, body: Hello RocketMQ OrderStep{orderId=100001, describe='创建'}
SendResult status:SEND_OK, queueId:2, body: Hello RocketMQ OrderStep{orderId=100002, describe='创建'}
SendResult status:SEND_OK, queueId:1, body: Hello RocketMQ OrderStep{orderId=100001, describe='付款'}
SendResult status:SEND_OK, queueId:3, body: Hello RocketMQ OrderStep{orderId=100003, describe='创建'}
SendResult status:SEND_OK, queueId:2, body: Hello RocketMQ OrderStep{orderId=100002, describe='付款'}
SendResult status:SEND_OK, queueId:3, body: Hello RocketMQ OrderStep{orderId=100003, describe='付款'}
SendResult status:SEND_OK, queueId:2, body: Hello RocketMQ OrderStep{orderId=100002, describe='推送'}
SendResult status:SEND_OK, queueId:2, body: Hello RocketMQ OrderStep{orderId=100002, describe='完成'}
SendResult status:SEND_OK, queueId:3, body: Hello RocketMQ OrderStep{orderId=100003, describe='推送'}
SendResult status:SEND_OK, queueId:1, body: Hello RocketMQ OrderStep{orderId=100001, describe='推送'}
SendResult status:SEND_OK, queueId:3, body: Hello RocketMQ OrderStep{orderId=100003, describe='完成'}
SendResult status:SEND_OK, queueId:1, body: Hello RocketMQ OrderStep{orderId=100001, describe='完成'}

可以看见一个细节:订单ID相同的数据,发送到了相同的队列中。

3. 消费者实现

public static void main(String[] args) throws MQClientException {
        // 创建 consumer 实例,并设置生产者组名
        DefaultMQPushConsumer consumer = new  DefaultMQPushConsumer("orderGroup");
        // 设置 NameServer 地址
        consumer.setNamesrvAddr("127.0.0.1:9876");
        /*
         * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费<br>
         * 如果非第一次启动,那么按照上次消费的位置继续消费
         */
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);

        // 设置 topic和tag
        consumer.subscribe("orderTopic", "orderTag");

        // 注册消息监听器
        consumer.registerMessageListener(new MessageListenerOrderly() {
            @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()));
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });
        consumer.start();
        System.out.println("Consumer Started.");
    }

打印结果:

consumeThread=ConsumeMessageThread_4, queueId=1, content: Hello RocketMQ OrderStep{orderId=100001, describe='创建'}
consumeThread=ConsumeMessageThread_5, queueId=2, content: Hello RocketMQ OrderStep{orderId=100002, describe='创建'}
consumeThread=ConsumeMessageThread_6, queueId=1, content: Hello RocketMQ OrderStep{orderId=100001, describe='付款'}
consumeThread=ConsumeMessageThread_7, queueId=3, content: Hello RocketMQ OrderStep{orderId=100003, describe='创建'}
consumeThread=ConsumeMessageThread_8, queueId=2, content: Hello RocketMQ OrderStep{orderId=100002, describe='付款'}
consumeThread=ConsumeMessageThread_9, queueId=3, content: Hello RocketMQ OrderStep{orderId=100003, describe='付款'}
consumeThread=ConsumeMessageThread_10, queueId=2, content: Hello RocketMQ OrderStep{orderId=100002, describe='推送'}
consumeThread=ConsumeMessageThread_11, queueId=2, content: Hello RocketMQ OrderStep{orderId=100002, describe='完成'}
consumeThread=ConsumeMessageThread_12, queueId=3, content: Hello RocketMQ OrderStep{orderId=100003, describe='推送'}
consumeThread=ConsumeMessageThread_13, queueId=1, content: Hello RocketMQ OrderStep{orderId=100001, describe='推送'}
consumeThread=ConsumeMessageThread_14, queueId=3, content: Hello RocketMQ OrderStep{orderId=100003, describe='完成'}
consumeThread=ConsumeMessageThread_15, queueId=1, content: Hello RocketMQ OrderStep{orderId=100001, describe='完成'}

可以看到每个 queue 有唯一的 consumer 线程来消费, 订单对每个queue(分区)有序


技 术 无 他, 唯 有 熟 尔。
知 其 然, 也 知 其 所 以 然。
踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。