How does RabbitMQ ensure that 99.99% of the messages in the queue are consumed?

1. Outline of this article

In fact, there is another scenario that needs to be considered: when the consumer receives the message, but the business logic has not been processed, the consumer hangs up, and the message is also considered lost? , For example, when a user places an order, the order center sends a message to the queue in RabbitMQ, and the point center receives this message and intends to add 20 points to the user who placed the order, but the points have not been added successfully yet, and the point center hangs up by itself , causing data problems.

So how to solve this problem?

In order to ensure that messages are successfully consumed by consumers, RabbitMQ provides a message acknowledgement mechanism . This article mainly explains how to use the message acknowledgement mechanism in RabbitMQ to ensure that messages are successfully consumed by consumers and avoid sudden downtime of consumers. Caused message loss.

2. Enable explicit Ack mode

Our code to start a consumer looks like this:

// 创建队列消费者
com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel) {
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope,
                               AMQP.BasicProperties properties, byte[] body) throws IOException {
        String message = new String(body, "UTF-8");
        System.out.println("Received Message '" + message + "'");
    }
};
channel.basicConsume(QUEUE_NAME, true, consumer);

The point here is channel.basicConsume(QUEUE_NAME, true, consumer);the second parameter of the method. Let's first look at the source code of basicConsume():

public String basicConsume(String queue, boolean autoAck, Consumer callback) throws IOException {
    return this.basicConsume(queue, autoAck, "", callback);
}

The autoAck parameter here refers to whether it is automatically confirmed. If it is set to true, RabbitMQ will automatically set the sent message as confirmation, and then delete it from memory (or disk), regardless of whether the consumer receives the message and processes it successfully; if Set to false, RabbitMQ will wait for the consumer to explicitly reply to the confirmation signal before deleting from memory (or disk).

It is recommended to set autoAck to false, so that consumers have enough time to process messages without worrying about message loss caused by consumer downtime during message processing.

At this point, the message in the queue is divided into two parts:

  1. Waiting for the message to be delivered to the consumer (the Ready part in the figure below)
  2. It has been delivered to the consumer, but has not received the consumer's confirmation signal (Unacked part in the figure below)

If RabbitMQ has not received the confirmation signal from the consumer, and the consumer who consumes the message has been disconnected, RabbitMQ will arrange for the message to re-enter the queue and wait for delivery to the next consumer. Of course, it may still be the original consumer. By.

RabbitMQ does not set an expiration time for unacknowledged messages. The only basis for judging whether the message needs to be re-delivered to consumers is whether the consumer connection that consumes the message has been disconnected. The reason for this design is that RabbitMQ allows consumers to consume a message. Messages can take a long time.

For ease of understanding, let's take a specific example, and we will use the DurableProducer above in the words of the producer:

package com.zwwhnly.springbootaction.rabbitmq.durable;

import com.rabbitmq.client.AMQP;
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 DurableProducer {
    private final static String EXCHANGE_NAME = "durable-exchange";
    private final static String QUEUE_NAME = "durable-queue";

    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", true);
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");

        // 发送消息
        String message = "durable exchange test";
        AMQP.BasicProperties props = new AMQP.BasicProperties().builder().deliveryMode(2).build();
        channel.basicPublish(EXCHANGE_NAME, "", props, message.getBytes());

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

Then create a new consumer AckConsumer class:

package com.zwwhnly.springbootaction.rabbitmq.ack;

import com.rabbitmq.client.*;

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

public class AckConsumer {
    private final static String QUEUE_NAME = "durable-queue";

    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();
        // 创建队列消费者
        com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                int result = 1 / 0;
                System.out.println("Received Message '" + message + "'");
            }
        };
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}

We first set the autoAck parameter to true, that is, automatic confirmation, and deliberately write an exception when consuming messages, then run the producer client to write the message into the queue, and then run the consumer client, and find that the message is not consumed successfully but but disappeared:

Then we set autoAck to false:

channel.basicConsume(QUEUE_NAME, false, consumer);

Run the producer client again to write the message to the queue, and then run the consumer client. At this time, although the consumer client still has a code exception, the message is still in the queue:

Then we delete the exception code in the consumer client, restart the consumer client, and find that the message consumption is successful, but the message has not been Ack:

Manually stop the consumer client and find that the message is in the Ready state again, ready to be re-delivered:

The reason why the message is consumed, but it is still in the Unacked state, is because we did not add explicit Ack code to the code:

String message = new String(body, "UTF-8");
//int result = 1 / 0;
System.out.println("Received Message '" + message + "'");

long deliveryTag = envelope.getDeliveryTag();
channel.basicAck(deliveryTag, false);

deliveryTag can be seen as the number of the message, which is a 64-bit long integer value.

At this point, run the consumer client and find that the message is successfully consumed and removed from the queue:

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://10.200.1.11:23101/article/api/json?id=324071902&siteId=291194637