Mecanismo de reconocimiento de mensajes de RabbitMQ (ACK)

1. Mecanismo de reconocimiento de mensajes (ACK)

Para garantizar que los mensajes lleguen a los consumidores de manera confiable desde la cola, RabbitMQ proporciona un mecanismo de reconocimiento de mensajes (Reconocimiento de mensajes). Los consumidores pueden especificar el parámetro autoAck cuando se suscriben a la cola. Cuando el parámetro autoAck es igual a falso, RabbitMQ esperará a que el consumidor responda explícitamente a la señal de confirmación antes de eliminar el mensaje de la memoria (o del disco) (de hecho, se marca como eliminado primero., Eliminado después). Cuando el parámetro autoAck es igual a verdadero, RabbitMQ configurará automáticamente el mensaje enviado como un reconocimiento y luego lo eliminará de la memoria (o del disco), independientemente de si el consumidor realmente consume estos mensajes.

Una vez que se adopta el mecanismo de confirmación de mensajes, siempre que el parámetro autoAck se establezca en falso, el consumidor tiene tiempo suficiente para procesar el mensaje (tarea) y no hay necesidad de preocuparse por la pérdida del mensaje después de que el proceso del consumidor se cuelga en el proceso de procesamiento del mensaje, porque RabbitMQ siempre lo esperará. El mensaje no es hasta que el consumidor llama explícitamente al comando Basic.Ack.

Cuando el parámetro autoAck es falso, para el lado del servidor RabbitMQ, el mensaje en la cola se divide en dos partes: una es el mensaje que espera ser entregado al consumidor; la otra es el mensaje que se ha entregado al consumidor, pero aún no se ha recibido la señal de confirmación del consumidor Noticias. Si el servidor RabbitMQ no ha recibido la señal de confirmación del consumidor, y el consumidor que consume este mensaje ha sido desconectado, el servidor hará los arreglos para que el mensaje vuelva a ingresar a la cola y espere la entrega al siguiente consumidor (o al consumidor original). ).

RabbitMQ no establecerá un tiempo de vencimiento para los mensajes no confirmados. La única base para determinar si el mensaje debe volver a enviarse al consumidor es si la conexión para consumir el mensaje se ha desconectado. El motivo de esta configuración es el tiempo que RabbitMQ permite al consumidor consumir un mensaje Puede ser mucho, mucho tiempo.

En la plataforma de administración web RabbitMQ, puede ver la cantidad de mensajes en el estado "Listo" y el estado "No reconocido" en la cola actual, correspondiente a la cantidad de mensajes que esperan ser entregados al consumidor y los mensajes que se han entregado. al consumidor pero no han recibido la señal de confirmación, respectivamente número. Como se muestra abajo:

El mecanismo de confirmación de mensajes de RabbitMQ se divide en dos categorías: confirmación del remitente y confirmación del receptor.

La confirmación del remitente se divide en: el productor al intercambio a la confirmación y el intercambio a la confirmación de la cola. Como se muestra abajo:

 

2. Confirmación de envío de mensaje

2.1 Método ConfirmCallback

ConfirmCallback es una interfaz de devolución de llamada. Después de que se envía un mensaje al Broker, se activa una devolución de llamada para confirmar si el mensaje ha llegado al servidor del Broker, es decir , solo confirma si ha llegado correctamente a Exchange.

Necesitamos agregar la siguiente configuración a la configuración del productor para indicar que la confirmación del editor está activada.

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

2.2 Método ReturnCallback

Al implementar la interfaz ReturnCallback, el mensaje de inicio se devuelve cuando el mensaje falla. Esta interfaz activa una devolución de llamada cuando el intercambio no puede enrutar la cola. Este método no se puede utilizar porque el intercambio y la cola están vinculados en el código. Si el mensaje se entrega con éxito al Broker. Casi no hay falla para vincular la cola, a menos que su código sea incorrecto.

Para utilizar esta interfaz, debe agregar una configuración a la configuración del productor para indicar que el editor regresa.

spring.rabbitmq.publisher-returns=true

[Ejemplo] El remitente realiza la función de confirmación de envío de mensajes (confirmación del intercambiador, confirmación de la cola).

(1) Cree el primer proyecto SpringBoot (proyecto de mensajería de rabbitmq-provider).

En el archivo de información de configuración pom.xml, agregue los archivos dependientes relacionados:

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

Configure el servicio RabbitMQ en el archivo de configuración application.yml:

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) Configurar la cola

En rabbitmq-provider (proyecto de envío de mensajes), configure la confirmación del mensaje, el nombre de la cola, etc., y envíe la cola a la administración de IoC, el código es el siguiente:

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) Crea un remitente

En rabbitmq-provider (proyecto de envío de mensajes), cree un remitente y envíe el mensaje utilizando el método rabbitTemplate.convertAndSend ().

Al mismo tiempo, el parámetro routingKey se escribe deliberadamente en el error en el código, de modo que debe enviar un mensaje de confirmación a la devolución de llamada de falla de la cola, el código es el siguiente:

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);
    }
}

Resultados del:

 

3. Confirmación de la recepción del mensaje

El consumidor confirma que el consumidor procesa la falla comercial que ocurre en la cola de escucha, como por ejemplo: ocurre una excepción, datos que no cumplen con los requisitos, etc., necesitamos manejar manualmente estos escenarios, como reenviar o descartar.

El mecanismo de confirmación de mensaje (ACK) de RabbitMQ se confirma automáticamente de forma predeterminada. La confirmación automática se confirmará inmediatamente después de que se envíe el mensaje al consumidor, pero existe la posibilidad de perder el mensaje. Si la lógica del consumidor arroja una excepción, si usar el rollback, es solo La consistencia de los datos está garantizada, pero el mensaje aún se pierde, es decir, el consumidor no procesa el mensaje con éxito, entonces es equivalente a perder el mensaje.

Los modos de confirmación de mensajes son:

  1. AcknowledgeMode.NONE: confirmación automática.
  2. AcknowledgeMode.AUTO: Confirmar según la situación.
  3. AcknowledgeMode.MANUAL: Confirmación manual.

Después de recibir el mensaje, el consumidor llama manualmente a Basic.Ack o Basic.Nack o Basic.Reject, y RabbitMQ considera que la entrega está completa después de recibir estos mensajes.

  1. Comando Basic.Ack: se utiliza para confirmar el mensaje actual.
  2. Comando Basic.Nack: se utiliza para negar el mensaje actual (nota: esta es una extensión RabbitMQ de AMQP 0-9-1).
  3. Comando Basic.Reject: se utiliza para rechazar el mensaje actual.

3.1 método basicAck

El método basicAck se utiliza para confirmar el mensaje actual. El método basicAck en la clase Channel se define de la siguiente manera:

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

Descripción de parámetros:

Etiqueta de entrega larga: un ID de identificación único. Cuando un consumidor se registra en RabbitMQ, se establecerá un canal. RabbitMQ utilizará el método basic.deliver para enviar mensajes al consumidor. Este método lleva una etiqueta de entrega, que representa a RabbitMQ en el canal. El ID de identificación único del mensaje entregado es un número entero positivo que aumenta monótonamente y el alcance de la etiqueta de entrega se limita al canal.

booleano múltiple: ya sea ​​procesamiento por lotes, cuando este parámetro es verdadero, puede confirmar todos los mensajes cuya etiqueta_envío sea menor o igual que el valor entrante a la vez.

3.2 método basicNack

El método basicNack se utiliza para negar el mensaje actual. Dado que el método basicReject solo puede rechazar un mensaje a la vez, si desea rechazar mensajes en lotes, puede usar el método basicNack. El cliente consumidor puede usar el método channel.basicNack para lograrlo, el método se define de la siguiente manera:

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

Descripción de parámetros:

deliveryTag largo: ID de identificación único.

múltiplo booleano: se ha explicado anteriormente.

cola booleana: si el parámetro de cola se establece en verdadero, RabbitMQ volverá a almacenar el mensaje en la cola para que pueda enviarse al siguiente consumidor suscrito; si el parámetro de cola se establece en falso, RabbitMQ moverá inmediatamente el mensaje de la cola Además, no se enviará a nuevos consumidores.

3.3 método basicReject

El método basicReject se utiliza para rechazar explícitamente el mensaje actual en lugar de confirmarlo. RabbitMQ introdujo el comando Basic.Reject en la versión 2.0.0. Los clientes consumidores pueden llamar al método channel.basicReject correspondiente para decirle a RabbitMQ que rechace este mensaje.

El método basicReject en la clase Channel se define de la siguiente manera:

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

Descripción de parámetros:

deliveryTag largo: ID de identificación único.

requeue booleana: Se ha explicado anteriormente.

[Ejemplo] El cliente consumidor se da cuenta de la confirmación de recepción del mensaje.

(1) Cree un segundo proyecto SpringBoot (proyecto de recepción de mensajes rabbitmq-consumer).

En el archivo de información de configuración pom.xml, agregue los archivos dependientes relacionados:

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

Configure el servicio RabbitMQ en el archivo de configuración application.yml.

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

(2) Información de configuración

En rabbitmq-consumer (proyecto de recepción de mensajes), configure la confirmación manual de mensajes y la confirmación de la recepción de mensajes.

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) Crear receptor

En rabbitmq-consumer (proyecto de recepción de mensajes), cree un receptor.

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);
        }
    }
}

Resultados del:

 

[Ejemplo] Configurado para monitorear múltiples colas y realizar diferentes confirmaciones de recepción de mensajes.

(1) Modificar la clase de configuración

En la clase de configuración RabbitMqConfig.java anterior, agregue varias colas.

(2) Modificar el destinatario

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);
        }
    }
}

Resultados del:

 

Supongo que te gusta

Origin blog.csdn.net/pan_junbiao/article/details/112956537
Recomendado
Clasificación