Spring Boot+RabbitMQ Explicación detallada de la cola de retraso basada en la cola de mensajes no entregados y el complemento

1. Cola de mensajes fallidos

1.1 Conceptos básicos

Las letras muertas son mensajes que no se pueden consumir. En términos generales, el productor entrega el mensaje al intermediario o directamente a la cola, y el consumidor saca el mensaje de la cola para su consumo, pero a veces algunos mensajes en la cola no se pueden consumir por razones específicas. -up El procesamiento se convierte en letra muerta, y la cola de letras muertas es una cola especialmente utilizada para procesar letras muertas.

Hay tres razones para las letras muertas:

  1. El mensaje TTL ha caducado (Time To Live), pero no se ha consumido
  2. La cola ha alcanzado la longitud máxima y no puede continuar agregando datos a MQ
  3. El mensaje fue rechazado (usando el método basic.reject o basic.nack) y requeue=false

inserte la descripción de la imagen aquí
TTL es un atributo de un mensaje o cola en RabbitMQ, que indica el tiempo máximo de supervivencia de un mensaje o de todos los mensajes en la cola. La unidad es milisegundos. En otras palabras, si un mensaje tiene el atributo TTL establecido o ingresa a la cola con el atributo TTL establecido, entonces si el mensaje no se consume dentro del tiempo establecido por el TTL, se convertirá en "letra muerta".Si se configuran tanto el TTL de cola como el TTL de mensajes, se utilizará el valor más pequeño

RabbitMQ proporciona dos formas de configurar TTL: configuración de mensajes TTL y configuración de cola TTL. La diferencia entre la configuración de mensajes TTL y la configuración de cola TTL:
si la cola establece el atributo TTL del mensaje, una vez que el mensaje caduque, la cola lo descartará (si la cola de mensajes no entregados está configurada, se arrojará a la cola de mensajes no entregados).
).

Y si el TTL está configurado para un solo mensaje, incluso si el mensaje caduca, es posible que no se descarte inmediatamente.Porque la caducidad del mensaje se determina antes de ser entregado al consumidor., si la cola actual tiene una gran acumulación de mensajes, los mensajes caducados aún pueden sobrevivir durante mucho tiempo; además, debe tenerse en cuenta que si el TTL no está configurado, significa que el mensaje nunca caducará.Si TTL se establece en 0, significa que, a menos que el mensaje pueda entregarse directamente al consumidor en este momento, el mensaje se descartará.

1.2 Combate letra muerta

Cree un proyecto Maven y agregue las siguientes dependencias a pom.xml:

   <dependencies>
       <dependency>
           <groupId>com.rabbitmq</groupId>
           <artifactId>amqp-client</artifactId>
           <version>5.7.2</version>
       </dependency>
   </dependencies>

Cree una clase de herramienta RabbitMQUtils para crear una fábrica de conexiones para conectarse a RabbitMQ.

public class RabbitMQUtils {
    
    
    /**
     * 创建工厂,连接工厂是重量型的,重复创建,消耗性能
     */
    private static ConnectionFactory connectionFactory;
    static {
    
    
        connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("");
        connectionFactory.setPassword("");
        connectionFactory.setVirtualHost("/");
    }

    /**
     * 获取连接
     * @throws
     */
    public static Connection getConnection() throws IOException, TimeoutException {
    
    
        return connectionFactory.newConnection();
    }

    /**
     * 关闭连接和通道
     * @param connection
     * @param channel
     */
    public static void closeConnectionAndChannel(Connection connection, Channel channel){
    
    
        try {
    
    
            channel.close();
            connection.close();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

1.2.1 Simular la caducidad del TTL del mensaje

Simule que el TTL del mensaje caduca y se produzca la letra muerta, el código de productor:

public class Producer {
    
    

    private static final String NORMAL_EXCHANGE = "normal_exchange";
    private static final String NORMAL_ROUTING_KEY = "normal_routing_key";

    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        //设置消息的TTL
        AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
        for (int i = 0; i < 10; i++) {
    
    
            String message = "info" + i;
            channel.basicPublish(NORMAL_EXCHANGE, NORMAL_ROUTING_KEY, properties, message.getBytes());
            System.out.println("生产者发送消息:" + message);
        }
    }
}

Código del consumidor 1, después del inicio, cierre el consumidor para simular que no puede recibir mensajes, de modo que los mensajes ingresen a la cola de mensajes no entregados a través del intercambio de mensajes no entregados (DLX, Dead-Letter-Exchange):

public class Consumer01 {
    
    
    private static final String NORMAL_EXCHANGE = "normal_exchange";
    private static final String DEAD_EXCHANGE = "dead_exchange";
    private static final String NORMAL_ROUTING_KEY = "normal_routing_key";
    private static final String NORMAL_DEAD_LETTER_ROUTING_KEY = "normal_dead_letter_routing_key";

    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        //声明死信交换机和普通交换机,类型为direct
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
        //声明死信队列,绑定到死信交换机
        String deadQueue= "dead-queue";
        channel.queueDeclare(deadQueue,false,false,false, null );
        channel.queueBind(deadQueue, DEAD_EXCHANGE, NORMAL_DEAD_LETTER_ROUTING_KEY);

        //声明普通队列,并绑定死信队列信息
        HashMap<String, Object> params = new HashMap<>();
        params.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        params.put("x-dead-letter-routing-key", NORMAL_DEAD_LETTER_ROUTING_KEY);
        String normalQueue = "normal-queue";
        channel.queueDeclare(normalQueue, false,false,false,params);
        channel.queueBind(normalQueue,NORMAL_EXCHANGE,NORMAL_ROUTING_KEY);
        channel.basicConsume(normalQueue, true, new DefaultConsumer(channel){
    
    
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                System.out.println("Consumer01接收普通队列的消息:"+new String(body));
            }
        });
    }
}

Código para el consumidor 2:

public class Consumer02 {
    
    
    private static final String DEAD_EXCHANGE = "dead_exchange";
    private static final String NORMAL_DEAD_LETTER_ROUTING_KEY = "normal_dead_letter_routing_key";

    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
        //声明死信队列,绑定到死信交换机
        String deadQueue= "dead-queue";
        channel.queueDeclare(deadQueue,false,false,false, null );
        channel.queueBind(deadQueue, DEAD_EXCHANGE, NORMAL_DEAD_LETTER_ROUTING_KEY);
        channel.basicConsume(deadQueue, true, new DefaultConsumer(channel){
    
    
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                System.out.println("Consumer02接收死信队列的消息:"+new String(body));
            }
        });
    }
}

Probemos y verifiquemos el estado de la cola a través de RabbitMQ.
inserte la descripción de la imagen aquí
Iniciemos al consumidor 2 para consumir mensajes en la cola de mensajes no entregados y verifiquemos el estado de la cola a través de RabbitMQ.
inserte la descripción de la imagen aquí

1.2.2 La cola de simulación alcanza la longitud máxima

Lo siguiente simula la situación en la que la cola alcanza la longitud máxima y el mensaje ingresa a la cola de mensajes no entregados:
(1) El código del productor del mensaje elimina el atributo TTL

public class Producer {
    
    

    private static final String NORMAL_EXCHANGE = "normal_exchange";
    private static final String NORMAL_ROUTING_KEY = "normal_routing_key";

    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        //设置消息的TTL
//        AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
        for (int i = 0; i < 10; i++) {
    
    
            String message = "info" + i;
            channel.basicPublish(NORMAL_EXCHANGE, NORMAL_ROUTING_KEY, null, message.getBytes());
            System.out.println("生产者发送消息:" + message);
        }
    }
}

(2) Elimine la cola normal original, modifique los parámetros de la cola normal en la clase del consumidor 1Consumer01 y establezca el número máximo de mensajes en la cola en 6.

       params.put("x-max-length", 6);

Inicie el programa para recrear la cola. Luego cierre el consumidor 1 y simule la situación en la que el mensaje ingresa a la cola de mensajes no entregados.
inserte la descripción de la imagen aquí

1.2.3 Mensaje simulado rechazado

Lo siguiente simula el escenario en el que se rechaza el mensaje
(1) Primero, el código del productor elimina la configuración del atributo TTL del mensaje
(2) Elimina la cola normal y modifica el código de la clase Consumer01 (consumidor 1):

public class Consumer01 {
    
    
    private static final String NORMAL_EXCHANGE = "normal_exchange";
    private static final String DEAD_EXCHANGE = "dead_exchange";
    private static final String NORMAL_ROUTING_KEY = "normal_routing_key";
    private static final String NORMAL_DEAD_LETTER_ROUTING_KEY = "normal_dead_letter_routing_key";

    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        Connection connection = RabbitMQUtils.getConnection();
        Channel channel = connection.createChannel();
        //声明死信交换机和普通交换机,类型为direct
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
        //声明死信队列,绑定到死信交换机
        String deadQueue= "dead-queue";
        channel.queueDeclare(deadQueue,false,false,false, null );
        channel.queueBind(deadQueue, DEAD_EXCHANGE, NORMAL_DEAD_LETTER_ROUTING_KEY);

        //声明普通队列,并绑定死信队列信息
        HashMap<String, Object> params = new HashMap<>();
        params.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        params.put("x-dead-letter-routing-key", NORMAL_DEAD_LETTER_ROUTING_KEY);
        //设置队列的最大消息数
//        params.put("x-max-length", 6);
        String normalQueue = "normal-queue";
        channel.queueDeclare(normalQueue, false,false,false,params);
        channel.queueBind(normalQueue,NORMAL_EXCHANGE,NORMAL_ROUTING_KEY);
        /*
        模拟消息被拒绝
         */
        boolean autoAck = false;
        channel.basicConsume(normalQueue, autoAck, new DefaultConsumer(channel){
    
    
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                String message = new String(body);
                if ("info5".equals(message)){
    
    
                    System.out.println("Consumer01接收普通队列的消息:"+ message);
                    channel.basicReject(envelope.getDeliveryTag(), false);
                }else{
    
    
                    System.out.println("Consumer01接收普通队列的消息:"+ message);
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        });
    }
}

Después de comenzar, cierre el consumidor para simular que no puede recibir mensajes, el código del consumidor 2 permanece sin cambios. Inicie el productor para enviar mensajes, luego inicie primero el consumidor 1 y luego inicie el programa del consumidor 2 para realizar pruebas.
inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

2. Introducción a la cola de retraso

Una cola de retraso es una cola que se utiliza para almacenar mensajes que deben procesarse en un momento específico. El interior de la cola está ordenado y la característica más importante es el atributo de retraso. Se espera que los mensajes en la cola de retraso se saquen y procesen después del tiempo especificado.

Ejemplos de escenarios de uso comunes para colas de retraso:

  1. Si el pedido no se paga en diez minutos, se cancelará automáticamente
  2. Para una tienda recién creada, si no se ha subido ningún producto dentro de los diez días, se enviará automáticamente un mensaje recordatorio.
  3. Después de que el usuario se registre exitosamente, si no inicia sesión dentro de los tres días, se enviará un recordatorio por SMS.
  4. El usuario inicia un reembolso y, si el reembolso no se procesa dentro de los tres días, se notificará al personal operativo correspondiente.
  5. Una vez programada la reunión, se debe notificar a todos los participantes diez minutos antes de la hora programada para unirse a la reunión.

Estos escenarios tienen la característica de que una determinada tarea debe completarse en un momento específico después o antes de un determinado evento, como por ejemplo: ocurre un evento de generación de pedido, verificar el estado de pago del pedido diez minutos después y luego procesar los impagos. cerrar el pedido; parece usar tareas cronometradas, sondear datos todo el tiempo, verificarlos una vez por segundo, sacar los datos que deben procesarse y luego finalizar el procesamiento. Si la cantidad de datos es relativamente pequeña, es posible hacerlo, por ejemplo: para el requisito de "liquidación automática si la factura no se paga dentro de una semana", si el tiempo no está estrictamente limitado, sino una semana en En un sentido amplio, luego ejecútelo todas las noches. Cronometrar las tareas para verificar todas las facturas impagas es de hecho una solución factible. Sin embargo, para escenarios con una cantidad relativamente grande de datos y una gran puntualidad, como: "Los pedidos se cerrarán si no se pagan en diez minutos", puede haber muchos datos de pedidos impagos en un corto período de tiempo, y incluso puede llegar a millones o incluso decenas de millones durante el evento. Nivel, obviamente no es aconsejable seguir utilizando el sondeo para una cantidad tan grande de datos, es probable que la verificación de todos los pedidos no se pueda completar en un segundo, y al mismo tiempo, ejercerá mucha presión sobre la base de datos, que no puede cumplir con los requisitos comerciales y tiene un rendimiento bajo.

inserte la descripción de la imagen aquí

3. Implementar cola de retraso basada en cola de mensajes no entregados

Podemos configurar el atributo TTL para que el mensaje se convierta en letra muerta justo después de cuánto tiempo se retrasa. Por otro lado, el mensaje que se convierte en letra muerta se entregará a la cola de mensajes no entregados, de modo que los consumidores solo necesiten consumir los mensajes en la cola de mensajes no entregados, el consumo retrasado de mensajes se puede completar y se puede lograr el efecto de la cola retrasada. El diagrama de arquitectura de código básico para implementar la cola de retraso basada en la cola de mensajes no entregados es el siguiente:
inserte la descripción de la imagen aquí

3.1 Preparación del entorno

Crear un proyecto Maven

3.1.1 Introducir dependencias

    <dependencies>
        <!--RabbitMQ依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--Swagger依赖-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--RabbitMQ测试依赖-->
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <version>2.2.3.RELEASE</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

3.1.2 Archivo de configuración

Configure el servidor RabbitMQ slim en el archivo de configuración application.properties

spring:
  application:
    name: springboot_rabbitmq
  rabbitmq:
    host: 198.143.320.72
    port: 5672
    username: guest
    password: guest
    virtual-host: /learn

3.1.3 Agregar configuración de Swagger

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    
    
    @Bean
    public Docket webApiConfig(){
    
    
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .build();
    }
    private ApiInfo webApiInfo(){
    
    
        return new ApiInfoBuilder()
                .title("rabbitmq 接口文档")
                .description("本文档描述了 rabbitmq 微服务接口定义")
                .version("1.0")
                .contact(new Contact("enjoy6288", "http://atguigu.com",
                        "[email protected]"))
                .build();
    } }

3.2 Configuración de cola TTL para lograr retraso

Cree dos colas QA y QB, y establezca el TTL de las dos colas en 10S y 40S respectivamente, y luego cree un interruptor X y un interruptor de mensajes no entregados Y, los cuales son directos, cree una cola de mensajes no entregados QD y su vinculación. relación de la siguiente manera:

inserte la descripción de la imagen aquí
El retraso de implementación TTL de configuración de la cola debe configurarse utilizando el parámetro x-message-ttl al definir la cola , y la unidad es "milisegundos".

3.2.1 Código de clase de configuración

Cree una clase de configuración TtlQueueConfig, que se utiliza para configurar colas y conmutadores y configurar la relación vinculante entre colas y conmutadores. Configure el tiempo de vencimiento de los mensajes en la cola, la unidad de tiempo predeterminada es milisegundos.

@EnableRabbit
@Configuration
public class TtlQueueConfig {
    
    
    private static final String X_CHANGE="X";
    private static final String QUEUE_A="QA";
    private static final String QUEUE_B="QB";


    private static final String Y_DEAD_LETTER_EXCHANGE="Y";
    private static final String DEAD_LETTER_QUEUE="QD";

    @Bean
    public DirectExchange xExchange(){
    
    
        return new DirectExchange(X_CHANGE);
    }

    @Bean
    public DirectExchange yExchange(){
    
    
        return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);
    }

    /**
     * 声明队列A 的消息ttl为10s,并绑定到对应的死信交换机
     *
     * @return
     */
    @Bean("queueA")
    public Queue queueA() {
    
    
        HashMap<String, Object> args = new HashMap<>();
        args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
        args.put("x-dead-letter-routing-key", "YD");
        args.put("x-message-ttl", 10000);
        return QueueBuilder.durable(QUEUE_A).withArguments(args).build();
    }

    /**
     *声明队列 A 绑定 X 交换机
     */
    @Bean
    public Binding queueaBindingX(@Qualifier("queueA") Queue queueA,
                                  @Qualifier("xExchange") DirectExchange xExchange){
    
    
        return BindingBuilder.bind(queueA).to(xExchange).with("XA");
    }

    /**
     * 声明队列 B ttl 为 40s 并绑定到对应的死信交换机
     * @return
     */
    @Bean("queueB")
    public Queue queueB(){
    
    
        Map<String, Object> args = new HashMap<>(3);
        //声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
        //声明当前队列的死信路由 key
        args.put("x-dead-letter-routing-key", "YD");
        //声明队列的 TTL
        args.put("x-message-ttl", 40000);
        return QueueBuilder.durable(QUEUE_B).withArguments(args).build();
    }
    /**
     *声明队列 B 绑定 X 交换机
     * @return
     */
    @Bean
    public Binding queuebBindingX(@Qualifier("queueB") Queue queue1B,
                                  @Qualifier("xExchange") DirectExchange xExchange){
    
    
        return BindingBuilder.bind(queue1B).to(xExchange).with("XB");
    }
    /**
     *声明死信队列 QD
     * @return
     */
    @Bean("queueD")
    public Queue queueD(){
    
    
        return new Queue(DEAD_LETTER_QUEUE);
    }
    /**
     *声明死信队列 QD 绑定关系
     * @return
     */
    @Bean
    public Binding deadLetterBindingQAD(@Qualifier("queueD") Queue queueD,
                                        @Qualifier("yExchange") DirectExchange yExchange){
    
    
        return BindingBuilder.bind(queueD).to(yExchange).with("YD");
    }
}

3.2.2 Código de Productor

@Slf4j
@RequestMapping("ttl")
@RestController
public class SendMsgController {
    
    

    @Autowired
    private RabbitTemplate rabbitTemplate;
    @GetMapping("sendMsg/{message}")
    public void sendMsg(@PathVariable String message){
    
    
        log.info("当前时间:{},发送一条信息给两个 TTL 队列:{}", new Date(), message);
        rabbitTemplate.convertAndSend("X", "XA", "消息来自 ttl 为 10S 的队列: "+message);
        rabbitTemplate.convertAndSend("X", "XB", "消息来自 ttl 为 40S 的队列: "+message);
    }
    
}

3.2.3 Código del Consumidor

@Slf4j
@Component
public class DeadLetterQueueConsumer {
    
    

    @RabbitListener(queues = "QD")
    public void receiveD(Message message, Channel channel) throws IOException {
    
    
        String msg = new String(message.getBody());
        log.info("当前时间:{},收到死信队列信息{}", new Date().toString(), msg);
    }
   
}

A través de Swagger: http://localhost:8080/swagger-ui.html, llame a la interfaz /ttl/sendMsg para realizar pruebas.
inserte la descripción de la imagen aquí

El primer mensaje se convierte en letra muerta después de 10S y luego es consumido por los consumidores. El segundo mensaje se convierte en un mensaje no entregado después de 40 segundos y luego se consume, completando así la función de implementar la cola de retraso. Sin embargo, al configurar el TTL del mensaje a través de la cola, cada vez que se agrega un nuevo requisito de tiempo, se debe agregar una nueva cola, por lo que este método solo es adecuado para escenarios de demanda con un tiempo de retraso fijo. Para escenarios de demanda donde el tiempo de retraso no es fijo, debe utilizar el mensaje para configurar el TTL para lograr el retraso.

3.3 Retraso en la implementación de TTL de configuración de mensajes

A continuación, retrase el mensaje configurando el TTL del mensaje. El TTL del mensaje se establece configurando el valor del parámetro de caducidad del atributo del mensaje, y la unidad es "milisegundos".

3.3.1 Código de clase de configuración

Cree una clase de configuración MsgTtlQueueConfig, que se utiliza para configurar colas, conmutadores y configurar la relación vinculante entre colas y conmutadores.

@Configuration
public class MsgTtlQueueConfig {
    
    

    private static final String DELAY_EXCHANGE_NAME="DELAY_EXCHANGE_NAME";

    private static final String DELAY_QUEUE="DELAY_QUEUE";

    private static final String DELAY_ROUTING_KEY="DELAY_ROUTING_KEY";

    private static final String DEAD_EXCHANGE_NAME="DEAD_EXCHANGE_NAME";

    private static final String DEAD_QUEUE="DEAD_QUEUE";

    private static final String DEAD_ROUTING_KEY="DEAD_ROUTING_KEY";


    /**
     * 死信队列,交换机定义
     * @return
     */
    @Bean
    public DirectExchange deadExchange(){
    
    
        return new DirectExchange(DEAD_EXCHANGE_NAME);
    }

    @Bean
    public Queue deadQueue() {
    
    
        return QueueBuilder.durable(DEAD_QUEUE).build();
    }

    @Bean
    public Binding deadExchangeBinding(){
    
    
        return BindingBuilder.bind(deadQueue()).to(deadExchange()).with(DEAD_ROUTING_KEY);
    }

    /**
     * 延迟队列,交换机定义
     * @return
     */
    @Bean
    public DirectExchange delayExchange(){
    
    
        return new DirectExchange(DELAY_EXCHANGE_NAME);
    }

    @Bean
    public Queue delayQueue() {
    
    
        return QueueBuilder.durable(DELAY_QUEUE)
                .withArgument("x-dead-letter-exchange", DEAD_EXCHANGE_NAME)
                .withArgument("x-dead-letter-routing-key", DEAD_ROUTING_KEY)
                .build();
    }

    @Bean
    public Binding delayQueueBinding(){
    
    
        return BindingBuilder.bind(delayQueue()).to(delayExchange()).with(DELAY_ROUTING_KEY);
    }

}

La configuración se divide en dos grupos, el primer grupo configura la cola común y el segundo grupo configura la cola de mensajes no entregados. Cada grupo está compuesto por cola de mensajes, conmutador y enlace.

3.3.2 Código de Productor

@Slf4j
@RequestMapping("ttl")
@RestController
public class SendMsgController {
    
    

    private static final String DELAY_EXCHANGE_NAME="DELAY_EXCHANGE_NAME";

    private static final String DELAY_ROUTING_KEY="DELAY_ROUTING_KEY";

    private static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";

    private static final String DELAYED_ROUTING_KEY = "delayed.routingkey";

    @GetMapping("sendDelayMsg/{message}/{delayTime}")
    public void sendMsg(@PathVariable String message,@PathVariable String delayTime) {
    
    
        rabbitTemplate.convertAndSend(DELAY_EXCHANGE_NAME, DELAY_ROUTING_KEY, message,
                correlationData ->{
    
    
                    correlationData.getMessageProperties().setExpiration(delayTime);

                    return correlationData;
                });
        log.info(" 基于死信队列实现,当 前 时 间 : {}, 发送一条延迟 {} 毫秒的信息给队列DELAY_QUEUE:{}", new
                Date(),delayTime, message);
    }

3.3.3 Código del Consumidor

@Slf4j
@Component
public class DeadLetterQueueConsumer {
    
    

    private static final String DEAD_QUEUE="DEAD_QUEUE";

    @RabbitListener(queues = DEAD_QUEUE)
    public void receiveDeadQueue(Message message){
    
    
        String msg = new String(message.getBody());
        log.info("当前时间:{},收到死信队列信息:{}", new Date().toString(), msg);
    }
}

A través de Swagger: http://localhost:8080/swagger-ui.html, llame a la interfaz /ttl/sendDelayMsg para realizar pruebas.
inserte la descripción de la imagen aquí
Al configurar el TTL en el atributo del mensaje, puede satisfacer las necesidades de diferentes tiempos de retraso del mismo tipo de mensaje, pero debido a que RabbitMQ solo verificará si el primer mensaje caduca, si caduca, será arrojado a la cola de mensajes no entregados. , si el primer mensaje Un tiempo de retraso prolongado afectará el consumo de mensajes posteriores. La simulación del escenario anterior es la siguiente:
inserte la descripción de la imagen aquí
se puede ver que el primer mensaje enviado con un retraso de 50000000 milisegundos afecta el consumo de mensajes posteriores. Los problemas anteriores se pueden resolver utilizando el complemento Rabbitmq_delayed_message_exchange proporcionado oficialmente por RabbitMQ.

4. El complemento RabbitMQ implementa la cola de retraso

Implemente una cola de retraso basada en el complemento RabbitMQ, solo necesita crear un conmutador y una cola, que es más fácil de usar que la cola de mensajes no entregados.

4.1 Instalar el complemento RabbitMQ

Descargue el complemento Rabbitmq_delayed_message_exchange

wget https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/download/v3.8.0/rabbitmq_delayed_message_exchange-3.8.0.ez

El enlace oficial de Github del complemento Rabbitmq_delayed_message_exchange: https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases

Copie el archivo en el contenedor Docker

docker cp rabbitmq_delayed_message_exchange-3.8.0.ez 900822f303cd:/opt/rabbitmq/plugins

Ingrese al contenedor RabbitMQ

docker exec -it 900822f303cd /bin/sh

habilitar complemento

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

Compruebe si el complemento se inició correctamente

rabbitmq-plugins list

Reinicie el contenedor RabbitMQ

inserte la descripción de la imagen aquí
Vea en la interfaz de administración de RabbitMQ, cree una página de cambio, si ve una opción adicional de "mensaje retrasado x" para el tipo de cambio, significa que el complemento se instaló correctamente.
inserte la descripción de la imagen aquí

El modificador type=x-delayed-message admite el mecanismo de entrega retrasada. Una vez entregado el mensaje, no se entregará a la cola de destino inmediatamente, sino que se almacenará en la tabla local de Mnesia (una base de datos distribuida). Cuando se alcanza el tiempo, se entregará a la cola de destino.

4.2 Ejemplo de uso

Cree una clase de configuración DelayedQueueConfig para configurar conmutadores y colas

@Configuration
public class DelayedQueueConfig {
    
    

    public static final String DELAYED_QUEUE_NAME = "delayed.queue";
    public static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";
    public static final String DELAYED_ROUTING_KEY = "delayed.routingkey";
    @Bean
    public Queue delayedQueue() {
    
    
        return new Queue(DELAYED_QUEUE_NAME);
    }

    /**
     * 自定义交换机 我们在这里定义的是一个延迟交换机
     */
    @Bean
    public CustomExchange delayedExchange() {
    
    
        Map<String, Object> args = new HashMap<>();
        //自定义交换机的类型
        args.put("x-delayed-type", "direct");
        return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message", true, false,
                args);
    }
    @Bean
    public Binding bindingDelayedQueue(@Qualifier("delayedQueue") Queue queue,
                                       @Qualifier("delayedExchange") CustomExchange
                                               delayedExchange) {
    
    
        return BindingBuilder.bind(queue).to(delayedExchange).with(DELAYED_ROUTING_KEY).noargs();
    } }

Crear código de productor

@Slf4j
@RequestMapping("ttl")
@RestController
public class SendMsgController {
    
    

    private static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";

    private static final String DELAYED_ROUTING_KEY = "delayed.routingkey";


    @GetMapping("sendDelayMsg2/{message}/{delayTime}")
    public void sendMsg2(@PathVariable String message,@PathVariable Integer delayTime) {
    
    
        rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_ROUTING_KEY, message,
                correlationData ->{
    
    
                    correlationData.getMessageProperties().setDelay(delayTime);

                    return correlationData;
                });
        log.info(" 基于插件实现,当 前 时 间 : {}, 发送一条延迟 {} 毫秒的信息给队列 delayed.queue:{}", new
                Date(),delayTime, message);
    }


}

Crear código de consumidor

@Slf4j
@Component
public class DeadLetterQueueConsumer {
    
    
    private static final String DELAYED_QUEUE_NAME = "delayed.queue";

    @RabbitListener(queues = DELAYED_QUEUE_NAME)
    public void receiveDelayedQueue(Message message){
    
    
        String msg = new String(message.getBody());
        log.info("当前时间:{},收到延时队列的消息:{}", new Date().toString(), msg);
    }

}

A través de Swagger: http://localhost:8080/swagger-ui.html, llame a la interfaz /ttl/sendDelayMsg2 para realizar pruebas.

inserte la descripción de la imagen aquí
A través de la prueba, se puede ver que incluso si el tiempo de demora del mensaje el primer día es muy largo, no afectará el consumo de mensajes posteriores.

Las notas están resumidas del curso: https://www.bilibili.com/video/BV1cb4y1o7zz
Más:
1. Cinco implementaciones de mensajes retrasados

Supongo que te gusta

Origin blog.csdn.net/huangjhai/article/details/121889828
Recomendado
Clasificación