RocketMQ custom message queue selector to ensure message order

How to ensure global order?

The simplest way to ensure global order is that there is only one queue in topic, which can ensure global order, but will anyone use it like this? Certainly not, because the performance, throughput and security will be very poor

Therefore, it is to ensure local order, not global order

Ensure orderly usage scenarios

For order payment, an order must come down in order. For example, it must be paid first, then marketed, and then sent to logistics. In this way, the order cannot be disordered

Or chat function: we all need to ensure the order of sending. It can't be said that you sent it first and then came after others

So this is to ensure local order

case

The producer sends 10 orders. There are six steps in each order. Each step will send a message. In the past, these six steps are required to be sequential

Consumers should ensure that the order of consumption is the same

All MQ S can only ensure that messages are ordered in one queue. If Kafka is used, it is Partition

RocketMQ guarantees local order, not global order

What is sequential local order? Partial order requires the cooperation of your producers and consumers

consumer

Note: when registering a Listener, registerMessageListener needs to use MessageListenerOrderly, and the extracted messages are orderly. If other messagelisteners, such as messagelistenercurrently, are used, the order cannot be guaranteed

package org.apache.rocketmq.example.ordermessage;

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;

public class Consumer {

    public static void main(String[] args) throws MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("please_rename_unique_group_name_3");
        consumer.setNamesrvAddr("zjj101:9876;zjj102:9876;zjj103:9876");
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);

        consumer.subscribe("OrderTopicTest", "*");
        /**
         *The consumer registers a listener, MessageListenerOrderly,
         * Get it from the queue
         */
        consumer.registerMessageListener(new MessageListenerOrderly() {
            /***
             *
             * @param msgs
             * @param context
             * @return
             */
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                context.setAutoCommit(true);
                for (MessageExt msg : msgs) {
                    System.out.println("Received message content " + new String(msg.getBody()));
                }
                return ConsumeOrderlyStatus.SUCCESS; // Return successful consumption ID
            }
        });

        // Messagelistenercurrently is taken indiscriminately, and the consumption order cannot be guaranteed

//        This will not guarantee the order of final consumption.
//        consumer.registerMessageListener(new MessageListenerConcurrently() {
//            @Override
//            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
//                for(MessageExt msg:msgs){
//                    System.out.println("received message content" + new String(msg.getBody()));
//                }
//                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
//            }
//        });

        consumer.start();
        System.out.printf("Consumer Started.%n");
    }

}

Custom message queue selector

I wrote the notes in detail, so I won't explain more,

In addition, you should pay attention to the fact that the String may have negative numbers when hashCode

Why is there a negative number after the string hashCode is completed? Read this blog: https://zjj1994.blog.csdn.net/article/details/120875055

package org.apache.rocketmq.example.ordermessage;

import org.apache.rocketmq.client.producer.MessageQueueSelector;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageQueue;

import java.util.List;

/**
 * Custom message queue selector
 */
public class MyMessageQueueSelector implements MessageQueueSelector {

    /**
     * Select queue
     *
     * @param mqs All messagequeues under this topic
     * @param msg Messages sent
     * @param arg This parameter is passed from the third parameter when the producer calls the send method
     * @return
     */
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {


        // orderId takes the Size of MessageQueue and obtains an index, which ensures that messages in the same Order are stored in the same queue

        String orderId = (String) arg; // Get the passed orderId

        // Perform hash operation
        //Below & integer.max_ The purpose of value is to copy the hashCode, and the negative number comes out
        int orderIdHashCode = orderId.hashCode() & Integer.MAX_VALUE;

        // The hashCode value and the length of the queue are taken as the remainder to get an integer
        int index = orderIdHashCode % mqs.size();

        // Get a queue through the remainder above and deliver messages to the queue, so as to ensure
        MessageQueue messageQueue = mqs.get(index);
        return messageQueue;
    }
}

producer

500 orders are generated. Each order has four steps, such as placing an order, paying, confirming receipt and evaluating. A message will be sent in each step, and the message cannot be out of order, that is, the messages that cannot be paid will come before the order message, so there is a business bug

package org.apache.rocketmq.example.ordermessage;

import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;
import org.apache.rocketmq.remoting.exception.RemotingException;

import java.io.UnsupportedEncodingException;
import java.util.UUID;

public class Producer {
    public static void main(String[] args) throws UnsupportedEncodingException {
        try {
            DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
            producer.setNamesrvAddr("zjj101:9876;zjj102:9876;zjj103:9876");
            producer.start();

            for (int i = 0; i < 500; i++) { // Generate 500 orders

                //Generate 50 orders, where the order number is replaced by uuid. In fact, the order id generation scheme of each company is different
                String orderId = UUID.randomUUID().toString();
                // Each order has four steps, such as placing an order, paying, confirming receipt and evaluating. Each step will send a message in the past, and the message cannot be out of order, that is, payment cannot come before placing an order
                for (int j = 1; j <= 4; j++) {


                    // Instantiate a message
                    Message msg =
                            new Message("OrderTopicTest", "orderTag", "KEY" + orderId,
                                    ("order Id:" + orderId + " step:" + j).getBytes(RemotingHelper.DEFAULT_CHARSET));

                    //Instantiate the message queue selector written by yourself
                    MyMessageQueueSelector myMessageQueueSelector = new MyMessageQueueSelector();

                    /*
                     * MessageQueueSelector Message queue selector is used to select the queue to which messages are sent
                     * @param Parameter 1: MSG message you sent
                     * @param Parameter 2: selector, message Queue selector,
                     * @param Parameter 3: args is passed to the parameter used by the message queue selector. This is the third parameter of the select method in myMessageQueueSelector
                     */
                    SendResult sendResult = producer.send(msg, myMessageQueueSelector, orderId);

                    System.out.printf("%s%n", sendResult);
                }
            }

            producer.shutdown();
        } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

results of enforcement

Start two consumers

Start two consumers first, and check allow multiple instances, so that one consumer can start two

A set of code starts two instances

Start producer production message

There is no demonstration here. Start it yourself

View consumer console

Check the console of a Consumer and find

Look at a few at random and find that the steps are orderly

exceptional case! Partially ordered

I adjusted the steps and demonstrated it after adjusting 10 steps,

The following is partial order. You see, although the messages of the same order id are not consumed together, their order is still orderly

The order id of 4214d0e9 is also mixed with the order id of dae37279, but the consumption of 4214d0e9 is still orderly. The steps are not disordered

This is partial order

Tags: RocketMQ

Posted on Wed, 20 Oct 2021 14:44:55 -0400 by jb60606