RabbitMQ -- persistence, release confirmation

Posted by harmor on Mon, 07 Mar 2022 11:43:05 +0100

It should have been updated yesterday, but when using the browser to access the web port of the server's RabbitMQ, chrome shows that it is not a private link and does not allow you to log in to the Edge. Baidu has found many problems and still can't solve them. The reason is that the server doesn't install SSL certificate and uses ip directly to access.

1. Persistence

When the RabbitMQ service is stopped, the messages sent by the message producer will not be lost. By default, queues and messages are ignored when RabbitMQ exits or crashes. In order to ensure that messages are not lost, both queues and messages need to be marked as persistent.

1.1 achieving persistence

  1. Queue persistence: when creating a queue, channel queueDeclare(); The second parameter is changed to true.
  2. Message persistence: when sending messages using the channel basicPublish(); Change the third parameter to: messageproperties PERSISTENT_ TEXT_ Plan represents a persistent message.
/**
 * @Description Persistent MQ
 * @date 2022/3/7 9:14
 */
public class Producer3 {

    private static final String LONG_QUEUE = "long_queue";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMQUtils.getChannel();

        // Persistent queue
        channel.queueDeclare(LONG_QUEUE,true,false,false,null);

        Scanner scanner = new Scanner(System.in);
        int i = 0;
        while (scanner.hasNext()){
            i++;
            String msg = scanner.next() + i;
            // Persistent message
            channel.basicPublish("",LONG_QUEUE, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));
            System.out.println("Send message:'" + msg + "'success");
        }
    }

}

However, there is also a cache interval point for storing messages. There is no real write to disk. The persistence guarantee is not strong enough, but it is more than enough for a simple queue.

1.2 unfair distribution

The polling distribution method is not applicable when the processing efficiency of consumers is different. Therefore, real fairness should follow the premise that those who can do more work.

Modify channel at the consumer basicQos(1); Indicates that unfair distribution is enabled

/**
 * @Description Unfair distribution to consumers
 * @date 2022/3/7 9:27
 */
public class Consumer2 {
    private static final String LONG_QUEUE = "long_queue";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMQUtils.getChannel();
        DeliverCallback deliverCallback = (consumerTag, message) -> {
            // Simulate concurrent sleep for 30 seconds
            try {
                Thread.sleep(30000);
                System.out.println("thread  B Receive message:"+ new String(message.getBody(), StandardCharsets.UTF_8));
                channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        // Set unfair distribution
        channel.basicQos(1);

        channel.basicConsume(LONG_QUEUE,false,deliverCallback,
                consumerTag -> {
                    System.out.println(consumerTag + "Consumer cancels consumption");
                });
    }
}

1.3 unfair distribution of tests

Test purpose: whether it can be realized, those who can do more work.
Test method: two consumers sleep different events to simulate different processing events. If the processing time (sleep time) is short and can process multiple messages, it means that the purpose is achieved.

First start the producer to create a queue, and then start two consumers respectively.

The producer sends four messages in sequence:

Thread A with short sleep time received three messages

Thread B with long sleep time only receives the second message:

Because thread B takes A long time to process messages, other messages are allocated to thread A.
The experiment is successful!

1.4 pre value

Both message sending and manual confirmation are completed asynchronously, so there is a buffer for unconfirmed messages. Developers hope to limit the size of the buffer to avoid unlimited unconfirmed messages in the buffer.

The expected value here is the above method channel basicQos(); If there is a message equal to the parameter on the current channel, the message will not be consumed in the current channel.

1.4.1 code test

Test method:

  1. Create two different consumers and give 5 2 expected values respectively.
  2. Specify 5 for those with long sleep time and 2 for those with short sleep time.
  3. If the message is obtained according to the specified expected value, it indicates that the test is successful, but it does not mean that it will be allocated according to 5 and 2. This is similar to the judgment of weight.

The code can modify the expected value according to the above code.

2. Release confirmation

Publish confirmation is the process in which the producer is notified to the producer after the queue confirmation is persisted after the producer publishes the message to the queue. This ensures that messages are not lost.

It should be noted that queue persistence needs to be turned on before confirmation publishing can be used.
Opening method: channel confirmSelect();

2.1 single confirmation release

It is a synchronous publishing method, that is, after sending a message, the subsequent messages will continue to be published only after confirming its publishing. If there is no confirmation within the specified time, an exception will be thrown. The disadvantage is that it is very slow.

/**
 * @Description Confirmation release - single confirmation
 * @date 2022/3/7 14:49
 */
public class SoloProducer {

    private static final int MESSAGE_COUNT = 100;

    private static final String QUEUE_NAME = "confirm_solo";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMQUtils.getChannel();
        // Generate queue
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        // Open confirmation release
        channel.confirmSelect();
        // Record start time
        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String msg = ""+i;
            channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));

            // Single release confirmation
            boolean flag = channel.waitForConfirms();
            if (flag){
                System.out.println("Send message:" + i);
            }
        }

        // Record end time
        long endTime = System.currentTimeMillis();
        System.out.println("send out" + MESSAGE_COUNT + "Message consumption:"+(endTime - beginTime) + "millisecond");   }

}

2.2 batch confirmation release

Batch by batch confirmation release can improve the throughput of the system. However, the disadvantage is that when a failure causes a problem in publishing, you need to save the whole batch in memory and republish it later.

/**
 * @Description Release - batch confirmation
 * @date 2022/3/7 14:49
 */
public class BatchProducer {

    private static final int MESSAGE_COUNT = 100;

    private static final String QUEUE_NAME = "confirm_batch";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMQUtils.getChannel();
        // Generate queue
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        // Open confirmation release
        channel.confirmSelect();
        // Set the number of batches to be confirmed once.
        int batchSize = MESSAGE_COUNT / 10;
        // Record start time
        long beginTime = System.currentTimeMillis();
        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String msg = ""+i;
            channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));

            // Batch release confirmation
            if (i % batchSize == 0){
                if (channel.waitForConfirms()){
                    System.out.println("Send message:" + i);
                }
            }

        }

        // Record end time
        long endTime = System.currentTimeMillis();
        System.out.println("send out" + MESSAGE_COUNT + "Message consumption:"+(endTime - beginTime) + "millisecond");
    }

}

Obviously, the efficiency is much higher than that of a single confirmation release.

2.3 asynchronous confirmation Publishing

The programming is more complex than the above two, but the cost performance is very high. The reliability and efficiency are much better. The callback function is used to achieve the reliability of message transmission.

/**
 * @Description Confirmation release - asynchronous confirmation
 * @date 2022/3/7 14:49
 */
public class AsyncProducer {

    private static final int MESSAGE_COUNT = 100;

    private static final String QUEUE_NAME = "confirm_async";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMQUtils.getChannel();
        // Generate queue
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        // Open confirmation release
        channel.confirmSelect();

        // Record start time
        long beginTime = System.currentTimeMillis();

        // Confirm successful callback
        ConfirmCallback ackCallback = (deliveryTab,multiple) ->{
            System.out.println("Confirmation success message:" + deliveryTab);
        };

        // Confirmation failure callback
        ConfirmCallback nackCallback = (deliveryTab,multiple) ->{
            System.out.println("Unacknowledged message:" + deliveryTab);
        };

        // Message listener
        /**
         * addConfirmListener:
         *                  1. Confirm the successful message;
         *                  2. Confirm the message of failure.
         */
        channel.addConfirmListener(ackCallback,nackCallback);

        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String msg = "" + i;
            channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));

        }


        // Record end time
        long endTime = System.currentTimeMillis();
        System.out.println("send out" + MESSAGE_COUNT + "Message consumption:"+(endTime - beginTime) + "millisecond");
    }

}

2.4 processing unconfirmed messages

The best way to handle it is to put the unacknowledged messages into a memory based queue that can be accessed by the publishing thread.

For example, the ConcurrentLinkedQueue can transfer messages between the confirm callbacks of the confirmation queue and the publishing thread.

Treatment method:

  1. Record all messages to be sent;
  2. Confirm that the deletion is successful at the publishing place;
  3. Print unconfirmed messages.

Using a hash table to store messages has the following advantages:

  1. You can associate needs with messages;
  2. Easily delete entries in batch;
  3. Support high concurrency.
ConcurrentSkipListMap<Long,String > map = new ConcurrentSkipListMap<>();
/**
 * @Description Asynchronously publish confirmation and process messages that are not successfully published
 * @date 2022/3/7 18:09
 */
public class AsyncProducerRemember {
    private static final int MESSAGE_COUNT = 100;

    private static final String QUEUE_NAME = "confirm_async_remember";

    public static void main(String[] args) throws Exception {
        Channel channel = RabbitMQUtils.getChannel();
        // Generate queue
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        // Open confirmation release
        channel.confirmSelect();

        // Thread safe and orderly a hash table, suitable for high concurrency
        ConcurrentSkipListMap< Long, String > map = new ConcurrentSkipListMap<>();

        // Record start time
        long beginTime = System.currentTimeMillis();

        // Confirm successful callback
        ConfirmCallback ackCallback = (deliveryTab, multiple) ->{
            //2. Delete at the confirmation of successful publishing;
            // Batch delete
            if (multiple){
                ConcurrentNavigableMap<Long, String> confirmMap = map.headMap(deliveryTab);
                confirmMap.clear();
            }else {
                // Delete separately
                map.remove(deliveryTab);
            }
            System.out.println("Confirmation success message:" + deliveryTab);
        };

        // Confirmation failure callback
        ConfirmCallback nackCallback = (deliveryTab,multiple) ->{
            // 3. Print unconfirmed messages.
            System.out.println("Unacknowledged message:" + map.get(deliveryTab) + ",Marking:" + deliveryTab);
        };

        // Message listener
        /**
         * addConfirmListener:
         *                  1. Confirm the successful message;
         *                  2. Confirm the message of failure.
         */
        channel.addConfirmListener(ackCallback,nackCallback);

        for (int i = 0; i < MESSAGE_COUNT; i++) {
            String msg = "" + i;
            channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,msg.getBytes(StandardCharsets.UTF_8));
            // 1. Record all messages to be sent;
            map.put(channel.getNextPublishSeqNo(),msg);
        }


        // Record end time
        long endTime = System.currentTimeMillis();
        System.out.println("send out" + MESSAGE_COUNT + "Message consumption:"+(endTime - beginTime) + "millisecond");
    }
}

2.5 summary

Obviously, in addition to some trouble in coding, asynchronous processing is much better than single processing and batch processing in processing time, efficiency and availability.

Topics: Java Middleware message queue