RabbitMQ message acknowledgement mechanism (ACK)

1. Message acknowledgement mechanism (ACK)

In order to ensure that messages reach consumers reliably from the queue, RabbitMQ provides a message acknowledgement mechanism (Message Acknowledgement). Consumers can specify the autoAck parameter when subscribing to the queue. When the autoAck parameter is equal to false, RabbitMQ will wait for the consumer to explicitly reply to the confirmation signal before removing the message from the memory (or disk) (in fact, it is marked for deletion first , Deleted later). When the autoAck parameter is equal to true, RabbitMQ will automatically set the sent message as an acknowledgement, and then delete it from the memory (or disk), regardless of whether the consumer actually consumes these messages.

After the message confirmation mechanism is adopted, as long as the autoAck parameter is set to false, the consumer has enough time to process the message (task), and there is no need to worry about the loss of the message after the consumer process hangs in the process of processing the message, because RabbitMQ will always wait to hold The message is not until the consumer explicitly calls the Basic.Ack command.

When the autoAck parameter is false, for the RabbitMQ server side, the message in the queue is divided into two parts: one is the message waiting to be delivered to the consumer; the other is the message that has been delivered to the consumer, but the consumer confirmation signal has not yet been received News. If the RabbitMQ server has not received the consumer's confirmation signal, and the consumer who consumes this message has been disconnected, the server will arrange for the message to re-enter the queue and wait for delivery to the next consumer (or the original That consumer).

RabbitMQ will not set an expiration time for unconfirmed messages. The only basis for it to determine whether the message needs to be re-delivered to the consumer is whether the connection to consume the message has been disconnected. The reason for this setting is the time that RabbitMQ allows the consumer to consume a message It can be a long, long time.

On RabbitMQ's web management platform, you can see the number of messages in the "Ready" state and "Unacknowledged" state in the current queue, corresponding to the number of messages waiting to be delivered to the consumer and the messages that have been delivered to the consumer but have not received the confirmation signal, respectively number. As shown below:

The RabbitMQ message confirmation mechanism is divided into two categories: sender confirmation and receiver confirmation.

The sender confirmation is divided into: the producer to the exchange to the confirmation, and the exchange to the queue confirmation. As shown below:

 

2. Message sending confirmation

2.1 ConfirmCallback method

ConfirmCallback is a callback interface. After a message is sent to the Broker, a callback is triggered to confirm whether the message has reached the Broker server, that is , it only confirms whether it has arrived in the Exchange correctly.

We need to add the following configuration to the configuration of the producer to indicate that the publisher confirmation is turned on.

spring.rabbitmq.publisher-confirm-type=correlated # 新版本
spring.rabbitmq.publisher-confirms=true # 老版本

2.2 ReturnCallback method

By implementing the ReturnCallback interface, the start message is returned when the message fails. This interface triggers a callback when the exchange cannot route to the queue. This method can not be used because the exchange and the queue are bound in the code. If the message is successfully delivered to the Broker There is almost no failure to bind the queue, unless your code is wrong.

To use this interface, you need to add a configuration to the producer configuration to indicate that the publisher returns.

spring.rabbitmq.publisher-returns=true

[Example] The sender realizes the message sending confirmation function (exchanger confirmation, queue confirmation).

(1) Create the first SpringBoot project (rabbitmq-provider messaging project).

In the pom.xml configuration information file, add related dependent files:

<!-- AMQP客户端 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
    <version>2.4.1</version>
</dependency>

Configure the RabbitMQ service in the application.yml configuration file:

spring:
  # 项目名称
  application:
    name: rabbitmq-provider
  # RabbitMQ服务配置
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    # 消息确认(ACK)
    publisher-confirm-type: correlated #确认消息已发送到交换机(Exchange)
    publisher-returns: true #确认消息已发送到队列(Queue)

(2) Configure the queue

In rabbitmq-provider (message sending project), configure message confirmation, queue name, etc., and submit the queue to IoC management, the code is as follows:

package com.pjb.config;

import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;

/**
 * RabbitMQ配置类
 * @author pan_junbiao
 **/
@Configuration
public class RabbitMqConfig
{
    public static final String QUEUE_NAME = "queue_name"; //队列名称
    public static final String EXCHANGE_NAME = "exchange_name"; //交换器名称
    public static final String ROUTING_KEY = "routing_key"; //路由键

    @Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory)
    {
        RabbitTemplate rabbitTemplate = new RabbitTemplate();
        rabbitTemplate.setConnectionFactory(connectionFactory);
        //设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
        rabbitTemplate.setMandatory(true);

        //确认消息送到交换机(Exchange)回调
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback()
        {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause)
            {
                System.out.println("\n确认消息送到交换机(Exchange)结果:");
                System.out.println("相关数据:" + correlationData);
                System.out.println("是否成功:" + ack);
                System.out.println("错误原因:" + cause);
            }
        });

        //确认消息送到队列(Queue)回调
        rabbitTemplate.setReturnsCallback(new RabbitTemplate.ReturnsCallback()
        {
            @Override
            public void returnedMessage(ReturnedMessage returnedMessage)
            {
                System.out.println("\n确认消息送到队列(Queue)结果:");
                System.out.println("发生消息:" + returnedMessage.getMessage());
                System.out.println("回应码:" + returnedMessage.getReplyCode());
                System.out.println("回应信息:" + returnedMessage.getReplyText());
                System.out.println("交换机:" + returnedMessage.getExchange());
                System.out.println("路由键:" + returnedMessage.getRoutingKey());
            }
        });
        return rabbitTemplate;
    }

    /**
     * 队列
     */
    @Bean
    public Queue queue()
    {
        /**
         * 创建队列,参数说明:
         * String name:队列名称。
         * boolean durable:设置是否持久化,默认是 false。durable 设置为 true 表示持久化,反之是非持久化。
         * 持久化的队列会存盘,在服务器重启的时候不会丢失相关信息。
         * boolean exclusive:设置是否排他,默认也是 false。为 true 则设置队列为排他。
         * boolean autoDelete:设置是否自动删除,为 true 则设置队列为自动删除,
         * 当没有生产者或者消费者使用此队列,该队列会自动删除。
         * Map<String, Object> arguments:设置队列的其他一些参数。
         */
        return new Queue(QUEUE_NAME, true, false, false, null);
    }

    /**
     * Direct交换器
     */
    @Bean
    public DirectExchange exchange()
    {
        /**
         * 创建交换器,参数说明:
         * String name:交换器名称
         * boolean durable:设置是否持久化,默认是 false。durable 设置为 true 表示持久化,反之是非持久化。
         * 持久化可以将交换器存盘,在服务器重启的时候不会丢失相关信息。
         * boolean autoDelete:设置是否自动删除,为 true 则设置队列为自动删除,
         */
        return new DirectExchange(EXCHANGE_NAME, true, false);
    }

    /**
     * 绑定
     */
    @Bean
    Binding binding(DirectExchange exchange, Queue queue)
    {
        //将队列和交换机绑定, 并设置用于匹配键:routingKey
        return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
    }
}

(3) Create a sender

In rabbitmq-provider (message sending project), create a sender and send the message using the rabbitTemplate.convertAndSend() method.

At the same time, the routingKey parameter is deliberately written into the error in the code, so that it should send a confirmation message to the queue failure callback, the code is as follows:

package com.pjb;

import com.pjb.config.RabbitMqConfig;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

/**
 * RabbitMq测试类
 * @author pan_junbiao
 **/
@SpringBootTest
public class RabbitMqTest
{
    @Autowired
    RabbitTemplate rabbitTemplate;

    @Test
    public void sendMessage() throws Exception
    {
        String message = "您好,欢迎访问 pan_junbiao的博客";

        //这里故意将routingKey参数写入错误,让其应发确认消息送到队列失败回调
        rabbitTemplate.convertAndSend(RabbitMqConfig.EXCHANGE_NAME, "no_queue_name", message);

        //由于这里使用的是测试方法,当测试方法结束,RabbitMQ相关的资源也就关闭了,
        //会导致消息确认的回调出现问题,所有加段延时
        Thread.sleep(2000);
    }
}

Results of the:

 

3. Confirmation of message receipt

The consumer confirms that the consumer processing business failure that occurs in the listening queue, such as: an exception occurs, data that does not meet the requirements, etc., we need to manually handle these scenarios, such as resending or discarding.

The RabbitMQ message confirmation mechanism (ACK) is automatically confirmed by default. The automatic confirmation will be confirmed immediately after the message is sent to the consumer, but there is a possibility of losing the message. If the consumer logic throws an exception, if you use the rollback, it is only The consistency of the data is guaranteed, but the message is still lost, that is, the consumer does not process the message successfully, then it is equivalent to losing the message.

The message confirmation modes are:

  1. AcknowledgeMode.NONE: automatic confirmation.
  2. AcknowledgeMode.AUTO: Confirm according to the situation.
  3. AcknowledgeMode.MANUAL: Manual confirmation.

After receiving the message, the consumer manually calls Basic.Ack or Basic.Nack or Basic.Reject, and RabbitMQ considers the delivery to be complete after receiving these messages.

  1. Basic.Ack command: used to confirm the current message.
  2. Basic.Nack command: used to negate the current message (note: this is the RabbitMQ extension of AMQP 0-9-1).
  3. Basic.Reject command: used to reject the current message.

3.1 basicAck method

The basicAck method is used to confirm the current message. The basicAck method in the Channel class is defined as follows:

void basicAck(long deliveryTag, boolean multiple) throws IOException;

Parameter Description:

Long deliveryTag: a unique identification ID. When a consumer registers with RabbitMQ, a Channel will be established. RabbitMQ will use the basic.deliver method to push messages to the consumer. This method carries a delivery tag, which represents RabbitMQ to the Channel The unique identification ID of the delivered message is a monotonically increasing positive integer, and the scope of the delivery tag is limited to Channel.

boolean multiple: Whether batch processing, when this parameter is true, you can confirm all messages whose delivery_tag is less than or equal to the incoming value at one time.

3.2 basicNack method

The basicNack method is used to negate the current message. Since the basicReject method can only reject one message at a time, if you want to reject messages in batches, you can use the basicNack method. Consumer client can use the channel.basicNack method to achieve, the method is defined as follows:

void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;

Parameter Description:

long deliveryTag: unique identification ID.

boolean multiple: It has been explained above.

boolean requeue: If the requeue parameter is set to true, RabbitMQ will re-store the message in the queue so that it can be sent to the next subscribed consumer; if the requeue parameter is set to false, RabbitMQ will immediately move the message from the queue In addition, it will not be sent to new consumers.

3.3 basicReject method

The basicReject method is used to explicitly reject the current message instead of confirming it. RabbitMQ introduced the Basic.Reject command in version 2.0.0. Consumer clients can call the corresponding channel.basicReject method to tell RabbitMQ to reject this message.

The basicReject method in the Channel class is defined as follows:

void basicReject(long deliveryTag, boolean requeue) throws IOException;

Parameter Description:

long deliveryTag: unique identification ID.

boolean requeue: It has been explained above.

[Example] The consumer client realizes message reception confirmation.

(1) Create a second SpringBoot project (rabbitmq-consumer message receiving project).

In the pom.xml configuration information file, add related dependent files:

<!-- AMQP客户端 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
    <version>2.4.1</version>
</dependency>

Configure the RabbitMQ service in the application.yml configuration file.

spring:
  # 项目名称
  application:
    name: rabbitmq-consumer
  # RabbitMQ服务配置
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest

(2) Configuration information

In rabbitmq-consumer (message receiving project), configure manual confirmation of messages and confirmation of message reception.

package com.pjb.config;

import com.pjb.receiver.Receiver;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * RabbitMQ配置类
 * @author pan_junbiao
 **/
@Configuration
public class RabbitMqConfig
{
    @Autowired
    private CachingConnectionFactory connectionFactory;

    @Autowired
    private Receiver receiver; //消息接收处理类

    @Bean
    public SimpleMessageListenerContainer simpleMessageListenerContainer()
    {
        //消费者数量,默认10
        int DEFAULT_CONCURRENT = 10;
    
        //每个消费者获取最大投递数量 默认50
        int DEFAULT_PREFETCH_COUNT = 50;
    
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
        container.setConcurrentConsumers(DEFAULT_CONCURRENT);
        container.setMaxConcurrentConsumers(DEFAULT_PREFETCH_COUNT);
    
        // RabbitMQ默认是自动确认,这里改为手动确认消息
        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
    
        //设置一个队列
        container.setQueueNames("queue_name");
    
        //如果同时设置多个如下: 前提是队列都是必须已经创建存在的
        //container.setQueueNames("TestDirectQueue","TestDirectQueue2","TestDirectQueue3");
        //另一种设置队列的方法,如果使用这种情况,那么要设置多个,就使用addQueues
        //container.setQueues(new Queue("TestDirectQueue",true));
        //container.addQueues(new Queue("TestDirectQueue2",true));
        //container.addQueues(new Queue("TestDirectQueue3",true));
    
        container.setMessageListener(receiver);
    
        return container;
    }
}

(3) Create receiver

In rabbitmq-consumer (message receiving project), create a receiver.

package com.pjb.receiver;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;

/**
 * 接收者
 * @author pan_junbiao
 **/
@Component
public class Receiver implements ChannelAwareMessageListener
{
    @Override
    public void onMessage(Message message, Channel channel) throws Exception
    {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try
        {
            System.out.println("接收消息: " + new String(message.getBody(), "UTF-8"));

            /**
             * 确认消息,参数说明:
             * long deliveryTag:唯一标识 ID。
             * boolean multiple:是否批处理,当该参数为 true 时,
             * 则可以一次性确认 deliveryTag 小于等于传入值的所有消息。
             */
            channel.basicAck(deliveryTag, true);

            /**
             * 否定消息,参数说明:
             * long deliveryTag:唯一标识 ID。
             * boolean multiple:是否批处理,当该参数为 true 时,
             * 则可以一次性确认 deliveryTag 小于等于传入值的所有消息。
             * boolean requeue:如果 requeue 参数设置为 true,
             * 则 RabbitMQ 会重新将这条消息存入队列,以便发送给下一个订阅的消费者;
             * 如果 requeue 参数设置为 false,则 RabbitMQ 立即会还把消息从队列中移除,
             * 而不会把它发送给新的消费者。
             */
            //channel.basicNack(deliveryTag, true, false);
        }
        catch (Exception e)
        {
            e.printStackTrace();

            /**
             * 拒绝消息,参数说明:
             * long deliveryTag:唯一标识 ID。
             * boolean requeue:如果 requeue 参数设置为 true,
             * 则 RabbitMQ 会重新将这条消息存入队列,以便发送给下一个订阅的消费者;
             * 如果 requeue 参数设置为 false,则 RabbitMQ 立即会还把消息从队列中移除,
             * 而不会把它发送给新的消费者。
             */
            channel.basicReject(deliveryTag, true);
        }
    }
}

Results of the:

 

[Example] Set to monitor multiple queues and perform different message reception confirmations.

(1) Modify the configuration class

In the above RabbitMqConfig.java configuration class, add multiple queues.

(2) Modify the recipient

package com.pjb.receiver;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.stereotype.Component;

/**
 * 接收者
 * @author pan_junbiao
 **/
@Component
public class Receiver implements ChannelAwareMessageListener
{
    @Override
    public void onMessage(Message message, Channel channel) throws Exception
    {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try
        {
            if ("queue_name".equals(message.getMessageProperties().getConsumerQueue()))
            {
                System.out.println("消费的消息来自的队列名为:"+message.getMessageProperties().getConsumerQueue());
                System.out.println("接收消息: " + new String(message.getBody(), "UTF-8"));
                System.out.println("执行queue_name中的消息的业务处理流程......");
            }

            if ("fanout.A".equals(message.getMessageProperties().getConsumerQueue()))
            {
                System.out.println("消费的消息来自的队列名为:" + message.getMessageProperties().getConsumerQueue());
                System.out.println("接收消息: " + new String(message.getBody(), "UTF-8"));
                System.out.println("执行fanout.A中的消息的业务处理流程......");
            }

            /**
             * 确认消息,参数说明:
             * long deliveryTag:唯一标识 ID。
             * boolean multiple:是否批处理,当该参数为 true 时,
             * 则可以一次性确认 deliveryTag 小于等于传入值的所有消息。
             */
            channel.basicAck(deliveryTag, true);

            /**
             * 否定消息,参数说明:
             * long deliveryTag:唯一标识 ID。
             * boolean multiple:是否批处理,当该参数为 true 时,
             * 则可以一次性确认 deliveryTag 小于等于传入值的所有消息。
             * boolean requeue:如果 requeue 参数设置为 true,
             * 则 RabbitMQ 会重新将这条消息存入队列,以便发送给下一个订阅的消费者;
             * 如果 requeue 参数设置为 false,则 RabbitMQ 立即会还把消息从队列中移除,
             * 而不会把它发送给新的消费者。
             */
            //channel.basicNack(deliveryTag, true, false);
        }
        catch (Exception e)
        {
            e.printStackTrace();

            /**
             * 拒绝消息,参数说明:
             * long deliveryTag:唯一标识 ID。
             * boolean requeue:如果 requeue 参数设置为 true,
             * 则 RabbitMQ 会重新将这条消息存入队列,以便发送给下一个订阅的消费者;
             * 如果 requeue 参数设置为 false,则 RabbitMQ 立即会还把消息从队列中移除,
             * 而不会把它发送给新的消费者。
             */
            channel.basicReject(deliveryTag, true);
        }
    }
}

Results of the:

 

Guess you like

Origin blog.csdn.net/pan_junbiao/article/details/112956537