How does RabbitMQ guarantee that 99.99% of messages are sent successfully?

1. Outline of this article

RabbitMQ provides the following mechanisms to solve this problem:

  1. Producer confirms
  2. Endurance
  3. Manual Ack

In this blog, we will first explain the producer confirmation mechanism, and the remaining mechanisms will be explained in separate blogs later.

2. Producer Confirmation

To ensure that the message is not lost, we must first ensure that the producer can successfully send the message to the RabbitMQ server.

But in the previous example, when the producer sent the message, did the message arrive at the server correctly? If no special configuration is performed, the operation of sending a message will not return any message to the producer by default, that is, the producer does not know whether the message has arrived at the server correctly by default.

We can also know from the return type of the basicPublish method:

public void basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body) throws IOException {
    this.basicPublish(exchange, routingKey, false, props, body);
}

channel.queueDeclare(QUEUE_NAME, false, false, false, null);For a better understanding, let's take the annotation from the previous producer Producer class :

package com.zwwhnly.springbootaction.rabbitmq.helloworld;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class Producer {
    private final static String QUEUE_NAME = "hello";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建连接
        ConnectionFactory factory = new ConnectionFactory();
        // 设置 RabbitMQ 的主机名
        factory.setHost("localhost");
        // 创建一个连接
        Connection connection = factory.newConnection();
        // 创建一个通道
        Channel channel = connection.createChannel();
        // 指定一个队列,不存在的话自动创建
        //channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 发送消息
        String message = "Hello World!";
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");
        // 关闭频道和连接
        channel.close();
        connection.close();
    }
}

At this point, the code is run, because the queue does not exist, the message must have no place to store, but the program does not error, that is, the message is lost but we do not know it.

RabblitMQ provides two solutions for this problem:

  1. Realized by transaction mechanism
  2. Through the sender confirm (publisher confirm) mechanism

3. Transaction Mechanism

There are three methods related to the transaction mechanism in the RabblitMQ client:

  1. channel.txSelect: used to set the current channel to transaction mode
  2. channel.txCommit: used to commit transactions
  3. channel.txRollback: used to roll back the transaction

Create a new transaction producer class TransactionProducer, the code is as follows:

package com.zwwhnly.springbootaction.rabbitmq.producerconfirm;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class TransactionProducer {
    private final static String QUEUE_NAME = "hello";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建连接
        ConnectionFactory factory = new ConnectionFactory();
        // 设置 RabbitMQ 的主机名
        factory.setHost("localhost");
        // 创建一个连接
        Connection connection = factory.newConnection();
        // 创建一个通道
        Channel channel = connection.createChannel();
        // 指定一个队列,不存在的话自动创建
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        channel.txSelect();

        // 发送消息
        String message = "Hello World!";
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());

        channel.txCommit();
        System.out.println(" [x] Sent '" + message + "'");

        // 关闭频道和连接
        channel.close();
        connection.close();
    }
}

Run the code and find that the queue was added successfully and the message was sent successfully:

Slightly modify the code to see the transaction rollback of the exception mechanism:

try {
    channel.txSelect();

    // 发送消息
    String message = "Hello World!";
    channel.basicPublish("", QUEUE_NAME, null, message.getBytes());

    int result = 1 / 0;

    channel.txCommit();
    System.out.println(" [x] Sent '" + message + "'");
} catch (IOException e) {
    e.printStackTrace();
    channel.txRollback();
}

Because int result = 1 / 0;the java.lang.ArithmeticException exception will definitely be triggered, the transaction will be rolled back and the message sending fails:

If you want to send multiple messages, you can put channel.basicPublish, channel.txCommit and other methods in the loop body, as shown below:

channel.txSelect();
int loopTimes = 10;

for (int i = 0; i < loopTimes; i++) {
    try {
        // 发送消息
        String message = "Hello World!" + i;
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());

        channel.txCommit();
        System.out.println(" [x] Sent '" + message + "'");
    } catch (IOException e) {
        e.printStackTrace();
        channel.txRollback();
    }
}

Although the transaction can solve the problem of message confirmation between the message sender and RabbitMQ, the transaction can be submitted successfully only if the message is successfully received by RabbitMQ, otherwise the transaction can be rolled back after the exception is caught. However, using the transaction mechanism will "suck" RabbitMQ's performance, so it is recommended to use the sender confirmation mechanism mentioned below.

4. Sender confirmation mechanism

The sender confirmation mechanism means that the producer sets the channel to confirm mode. Once the channel enters the confirm mode, all messages published on the channel will be assigned a unique ID (starting from 1). Once the message is delivered to the After the RabbitMQ server, RabbitMQ will send an acknowledgment (Basic.Ack) to the producer (containing the unique ID of the message), which makes the producer know that the message has arrived at the destination correctly.

If RabbitMQ loses a message due to its own internal error, a nack (Basic.Nack) command is sent, and the producer application can also process the nack command in the callback method.

If the message and queue are durable, then the acknowledgment message is sent after the message is written to disk.

The transaction mechanism blocks the sender after a message is sent, waiting for a response from RabbitMQ before continuing to send the next message.

In contrast, the biggest benefit of the sender acknowledgment mechanism is that it is asynchronous once a message is published. The producer application can continue to send the next message while waiting for the channel to return confirmation. When the message is finally confirmed, the producer application can process the confirmation message through the callback method.

4.1 Ordinary confirm

Create a new confirmation production class NormalConfirmProducer, the code is as follows:

package com.zwwhnly.springbootaction.rabbitmq.producerconfirm;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class NormalConfirmProducer {
    private final static String EXCHANGE_NAME = "normal-confirm-exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建连接
        ConnectionFactory factory = new ConnectionFactory();
        // 设置 RabbitMQ 的主机名
        factory.setHost("localhost");
        // 创建一个连接
        Connection connection = factory.newConnection();
        // 创建一个通道
        Channel channel = connection.createChannel();
        // 创建一个Exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");

        try {
            channel.confirmSelect();
            // 发送消息
            String message = "normal confirm test";
            channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
            if (channel.waitForConfirms()) {
                System.out.println("send message success");
            } else {
                System.out.println("send message failed");
                // do something else...
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 关闭频道和连接
        channel.close();
        connection.close();
    }
}

channel.confirmSelect(); Set the channel to confirm mode.

channel.waitForConfirms();Waiting for a confirmation message to send the message, if the sending is successful, it returns true, if the sending fails, it returns false.

If you want to send multiple messages, you can put channel.basicPublish, channel.waitForConfirms and other methods in the loop body, as shown below:

channel.confirmSelect();
int loopTimes = 10;

for (int i = 0; i < loopTimes; i++) {
    try {
        // 发送消息
        String message = "normal confirm test" + i;
        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
        if (channel.waitForConfirms()) {
            System.out.println("send message success");
        } else {
            System.out.println("send message failed");
            // do something else...
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

operation result:

send message success

send message success

send message success

send message success

send message success

send message success

send message success

send message success

send message success

send message success

If the channel's confirm mode is not enabled, calling channel.waitForConfirms() will report an error:

Precautions:

1) The transaction mechanism and the publisher confirm mechanism are mutually exclusive and cannot coexist.

RabbitMQ will report an error if an attempt is made to set a channel that has already opened transaction mode to publisher confirm mode again:

channel.txSelect();
channel.confirmSelect();

RabbitMQ will also report an error if an attempt is made to set the channel that has enabled publisher confirm mode to transaction mode:

channel.confirmSelect();
channel.txSelect();

2) The transaction mechanism and the publisher confirm mechanism ensure that the message can be sent to RabbitMQ correctly. The meaning of "send to RabbitMQ" here means that the message is correctly sent to the RabbitMQ exchange, if the exchange does not have a matching queue , then the message will also be lost. So when using these two mechanisms, make sure that the exchanges involved can have matching queues.

For example, the message sent by the NormalConfirmProducer class above is sent to the exchange normal-confirm-exchange, but the exchange is not bound to any queue. From a business perspective, the message is still lost.

The normal confirm mode is to call the channel.waitForConfirms() method every time a message is sent, and then wait for the confirmation from the server, which is actually a serial synchronous waiting method. Therefore, compared with the transaction mechanism, the performance improvement is not much.

4.2 Batch confirm

The batch confirm mode is to call the channel.waitForConfirms() method after each batch of messages is sent, and wait for the confirmation from the server to return, so it has better performance than the normal confirm mode in 5.1.

But the downside is that if there is a return to Basic.Nack or a timeout, the producer client needs to resend all the messages in this batch, which will bring about an obvious number of duplicate messages. If messages are often lost, batch confirm The performance of the mode should not increase but decrease.

Code example:

package com.zwwhnly.springbootaction.rabbitmq.producerconfirm;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;

public class BatchConfirmProducer {
    private final static String EXCHANGE_NAME = "batch-confirm-exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建连接
        ConnectionFactory factory = new ConnectionFactory();
        // 设置 RabbitMQ 的主机名
        factory.setHost("localhost");
        // 创建一个连接
        Connection connection = factory.newConnection();
        // 创建一个通道
        Channel channel = connection.createChannel();
        // 创建一个Exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");

        int batchCount = 100;
        int msgCount = 0;
        BlockingQueue blockingQueue = new ArrayBlockingQueue(100);
        try {
            channel.confirmSelect();
            while (msgCount <= batchCount) {
                String message = "batch confirm test";
                channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
                // 将发送出去的消息存入缓存中,缓存可以是一个ArrayList或者BlockingQueue之类的
                blockingQueue.add(message);
                if (++msgCount >= batchCount) {
                    try {
                        if (channel.waitForConfirms()) {
                            // 将缓存中的消息清空
                            blockingQueue.clear();
                        } else {
                            // 将缓存中的消息重新发送
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        // 将缓存中的消息重新发送
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 关闭频道和连接
        channel.close();
        connection.close();
    }
}

4.3 Asynchronous confirm

The asynchronous confirm mode is to add the ConfirmListener callback interface on the producer client, and rewrite the handAck() and handNack() methods of the interface to process the Basic.Ack and Basic.Nack returned by RabblitMQ, respectively.

Both methods have two parameters. The first parameter, deliveryTag, is used to mark the unique sequence number of the message. The second parameter, multiple, indicates whether there are multiple confirmations. The value is true for multiple confirmations, and the value is false. Representation is a single confirmation.

Sample code:

package com.zwwhnly.springbootaction.rabbitmq.producerconfirm;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.TimeoutException;

public class AsyncConfirmProducer {

    private final static String EXCHANGE_NAME = "async-confirm-exchange";

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建连接
        ConnectionFactory factory = new ConnectionFactory();
        // 设置 RabbitMQ 的主机名
        factory.setHost("localhost");
        // 创建一个连接
        Connection connection = factory.newConnection();
        // 创建一个通道
        Channel channel = connection.createChannel();
        // 创建一个Exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");

        int batchCount = 100;
        long msgCount = 1;
        SortedSet<Long> confirmSet = new TreeSet<Long>();
        channel.confirmSelect();
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("Ack,SeqNo:" + deliveryTag + ",multiple:" + multiple);
                if (multiple) {
                    confirmSet.headSet(deliveryTag - 1).clear();
                } else {
                    confirmSet.remove(deliveryTag);
                }
            }

            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("Nack,SeqNo:" + deliveryTag + ",multiple:" + multiple);
                if (multiple) {
                    confirmSet.headSet(deliveryTag - 1).clear();
                } else {
                    confirmSet.remove(deliveryTag);
                }
                // 注意这里需要添加处理消息重发的场景
            }
        });
        // 演示发送100个消息
        while (msgCount <= batchCount) {
            long nextSeqNo = channel.getNextPublishSeqNo();
            channel.basicPublish(EXCHANGE_NAME, "", null, "async confirm test".getBytes());
            confirmSet.add(nextSeqNo);
            msgCount = nextSeqNo;
        }

        // 关闭频道和连接
        channel.close();
        connection.close();
    }
}

operation result:

Ack,SeqNo:1,multiple:false

Ack,SeqNo:2,multiple:false

Ack,SeqNo:3,multiple:false

Ack,SeqNo:4,multiple:false

Ack,SeqNo:5,multiple:false

Ack,SeqNo:6,multiple:false

Ack,SeqNo:7,multiple:false

Ack,SeqNo:8,multiple:false

Ack,SeqNo:9,multiple:false

Ack,SeqNo:10,multiple:false

Note: After multiple runs, it is found that the output results of each run are different, indicating that the ack message returned by RabbitMQ to the producer is not returned in a fixed batch size.

5. Performance comparison

So far, we have learned that 4 modes (transaction mechanism, normal confirm, batch confirm, asynchronous confirm) can achieve producer confirmation. Let's compare their performance and simply modify the number of messages sent in the above sample code. For example, 10000, the following are the time-consuming of 4 modes:

Sending 10000 messages, the transaction mechanism takes 2103

Sending 10000 messages, the ordinary confirm mechanism takes time: 1483

Sending 10,000 messages, the batch confirm mechanism takes time: 281

Sending 10000 messages, the asynchronous confirm mechanism takes time: 214

It can be seen that the transaction mechanism is the slowest. Although the ordinary confirm mechanism has been improved, it is not much. Batch confirm and asynchronous confirm have the best performance. You can choose which mechanism to use according to your own preferences. Personally, it is recommended to use the asynchronous confirm mechanism.

Pay attention, don't get lost, this is a QR code that programmers want to pay attention to

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324103254&siteId=291194637