Cómo garantizar la confiabilidad del mensaje + cola de demora (TTL + cola de mensajes fallidos + cola de demora)

Tabla de contenido

1. Cómo garantizar la fiabilidad del mensaje

1.1 Entrega confiable de mensajes

mecanismo de confirmación

mecanismo de retorno

1.2 Cómo asegurarse de que los mensajes no se pierdan en la cola

1.3 Garantizar que los mensajes se puedan consumir de forma fiable

2. Cola de retraso

2.1.TTL

2.2 Cola de mensajes fallidos

2.3 Cola de retraso

3. Cómo evitar que los consumidores consuman mensajes repetidamente


1. Cómo garantizar la fiabilidad del mensaje

1.1 Entrega confiable de mensajes

En el entorno de producción, debido a algunas razones desconocidas, rabbitmq se reinicia. Durante el reinicio de RabbitMQ, la entrega de mensajes del productor falla, lo que provoca la pérdida de mensajes, lo que requiere procesamiento y recuperación manuales. Entonces, comenzamos a pensar en cómo entregar mensajes de RabbitMQ de manera confiable. Especialmente en una situación tan extrema, cuando el clúster RabbitMQ no está disponible, ¿cómo lidiar con los mensajes que no se pueden entregar?

Al usar RabbitMQ, como remitente de mensajes, desea evitar cualquier pérdida de mensajes o escenarios de fallas en la entrega. RabbitMQ nos proporciona dos formas de controlar el modo de confiabilidad de la entrega de mensajes.

  • confirmar modo de confirmación
  • modo de retorno
  • El mensaje del productor al intercambio devolverá un confirmCallback.
  • Si el mensaje no se entrega desde el intercambio-->cola, se devolverá una devolución de llamada.

Por defecto, rabbitmq no habilita los dos modos anteriores

Usaremos estas dos devoluciones de llamada para controlar la entrega confiable de mensajes

Implementación de confirmar y devolver  

  1. Establezca Publisher-confirm-type: correlacionado de ConnectionFactory para habilitar el modo de confirmación.

  2. Use rabbitTemplate.setConfirmCallback para configurar la función de devolución de llamada. Vuelva a llamar al método de confirmación cuando el mensaje se envíe al intercambio. Juzgue el acuse de recibo en el método, si es verdadero, el envío es exitoso, si es falso, el envío falla y necesita ser procesado.

  3. Establezca publisher-returns="true" de ConnectionFactory para habilitar el modo de retorno.

  4. Use rabbitTemplate.setReturnCallback para configurar la función de devolución y ejecute la función de devolución de llamada devueltoMessage cuando el mensaje no se puede enrutar desde el intercambio a la cola.

mecanismo de confirmación

Demostración: 4. springboot integra RabbitMQ

(1) Active la confirmación en el archivo de configuración

#开启confirm确认机制
spring.rabbitmq.publisher-confirm-type=correlated

(2) Configure la función de devolución de llamada confirmCallback de rabbitTemplate

    /**
     * 使用confirm机制:
     * (1)需要开启confirm机制。-----配置文件中加入:spring.rabbitmq.publisher-confirm-type=correlated
     * (2)为rabbitTemplate指定setConfirmCallback回调函数
     */
    @Test
    void test001() {
        //只能保证消息从生产者到交换机的可靠性
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                //触发该方法
                if (ack == false) {
                    System.out.println("未来根据项目的需要,完成相应的操作");
                }
            }
        });
        rabbitTemplate.convertAndSend("Topics-exchange", "lazy.aaa", "Hello RabbitMQ...");
    }

mecanismo de retorno

(1) Activar retorno en el archivo de configuración

#开启return机制
spring.rabbitmq.publisher-returns=true

(2) Establecer la función de devolución de llamada de rabbitTemplate

    /**
     * return机制:
     * (1)开启return机制---配置文件加入:spring.rabbitmq.publisher-returns=true
     * (2)为rabbitTemplate设置return的回调函数
     */
    @Test
    void test002() {
        //只有当消息无法从交换机到队列时才会触发
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                //为交换机到队列分发消息失败时会触发
                System.out.println("replyCode====" + replyCode);
                System.out.println("replyText====" + replyText);
            }
        });
        rabbitTemplate.convertAndSend("Topics-exchange", "lazy.aaa", "Hello RabbitMQ...");

    }

1.2 Cómo asegurarse de que los mensajes no se pierdan en la cola

(1) Establecer la cola como persistente

(2) Establecer la persistencia del mensaje

1.3 Garantizar que los mensajes se puedan consumir de forma fiable

mecanismo de confirmación ACK

Múltiples consumidores reciben mensajes al mismo tiempo. A mitad de recibir mensajes, un consumidor cuelga repentinamente. Para garantizar que este mensaje no se pierda, se requiere un mecanismo de reconocimiento, es decir, el consumidor debe notificar al servidor después del consumo, y el servidor borrará los datos.

Esto resuelve el caso de que incluso si un consumidor tiene un problema y no hay un mensaje sincrónico al servidor, hay otros consumidores para consumir, asegurando que el mensaje no se pierda.

Implementación de ACK

ack se refiere a Reconocimiento, confirmación. Indica el método de confirmación del consumidor después de recibir el mensaje.

Hay tres métodos de confirmación:

  • Confirmación automática: reconocer = "ninguno"
  • Confirmación manual: reconocer="manual"
  • Confirme de acuerdo con la situación anormal: reconocer = "auto" (este método es problemático de usar y no se usa comúnmente)

La confirmación automática significa que una vez que el Consumidor recibe el mensaje, automáticamente confirmará el recibo y eliminará el mensaje correspondiente de la cola de mensajes de RabbitMQ. Sin embargo, en el procesamiento comercial real, es muy probable que se reciba el mensaje y el procesamiento comercial sea anormal, y el mensaje se perderá.

Si el método de confirmación manual está configurado , debe llamar a channel.basicAck() para firmar el recibo manualmente después de que el procesamiento comercial sea exitoso. Si hay una excepción, llame al método channel.basicNack() para permitir que reenvíe automáticamente el mensaje.

Lado del consumidor:

(1) Modifique el lado del consumidor: confirme manualmente el mensaje

#修改消费端----手动确认消息
spring.rabbitmq.listener.simple.acknowledge-mode=manual

(2).Modificar el código

  

package com.wqg.listener;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import org.springframework.amqp.core.Message;

import java.io.IOException;



/**
 * @ fileName:MyListener
 * @ description:
 * @ author:wqg
 * @ createTime:2023/7/12 18:57
 */
@Component
public class MyListener {

    //basicAck:确认消息----rabbit服务端删除
    //basicNack:服务继续发送消息
    @RabbitListener(queues = {"Topics-queue002"})//queues:表示你监听的队列名
    public void h(Message message , Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        //把监听到的消息封装到Message类对象中
        byte[] body = message.getBody();
        String s = new String(body);
        System.out.println("消息内容==="+s);
        try {
            //int a = 10/0; //模拟宕机
            System.out.println("核心业务的处理~~~");
            /**
             *long deliveryTag: 消息的标注
             * boolean multiple:是否把该消息之前未确认的消息一起确认掉
             */
            channel.basicAck(deliveryTag,true);//确认消息
        } catch (Exception e) {
            /**
             * long deliveryTag; boolean multiple;
             * boolean requeue:是否要求rabbitmq服务重新发送该消息
             */
            channel.basicNack(deliveryTag,true,true);
        }
    }
}

Resumen: ¿Cómo garantizar la fiabilidad del mensaje?

  • Entrega confiable garantizada de mensajes: mecanismo de confirmación y mecanismo de devolución
  • En la cola: --- persistencia
  • Utilice el mecanismo ACK para garantizar el consumo confiable de los consumidores.

2. Cola de retraso

2.1.TTL

El nombre completo de TTL es Time To Live (tiempo de vida/tiempo de caducidad).

Cuando el mensaje alcance el tiempo de supervivencia, se borrará automáticamente si no se ha consumido.

RabbitMQ puede establecer el tiempo de vencimiento del mensaje o establecer el tiempo de vencimiento para toda la cola (Cola).

Manifestación:

Creado usando una interfaz gráfica

 prueba de productor

 

 resultado

 

La cola se establece con un tiempo de caducidad y el mensaje también se establece con un tiempo de caducidad ----- ejecutar según el tiempo más corto 

resumen:

  • Para configurar el tiempo de caducidad de la cola, use el parámetro: x-message-ttl, unidad: ms (milisegundos), que caducará uniformemente todo el mensaje de la cola.
  • Establezca el tiempo de caducidad del mensaje utilizando el parámetro: caducidad. Unidad: ms (milisegundos), cuando el mensaje está al principio de la cola (cuando se consume), se juzgará si el mensaje está caducado o no.
  • Si ambos están configurados, prevalecerá el tiempo más corto.

2.2 Cola de mensajes fallidos

Cola de mensajes fallidos, abreviatura en inglés: DLX. Dead Letter Exchange (intercambio de letra muerta), cuando el mensaje se convierte en mensaje Dead, se puede reenviar a otro intercambio, este intercambio es DLX.

Qué tipo de mensajes se convierten en mensajes de letra muerta:

  • La longitud del mensaje en cola alcanza el límite
  • El consumidor rechaza el mensaje de consumo, basicNack/basicReject, y no vuelve a colocar el mensaje en la cola de destino original, requeue=false
  • Hay una configuración de caducidad de mensajes en la cola original y el tiempo de espera de llegada de mensajes no se ha consumido

Intercambio de mensajes fallidos de enlace de cola:

Manifestación:

Crear usando una GUI:

 

 

 

 prueba de productor

 resultado

 

2.3 Cola de retraso

Cola de retraso, es decir, el mensaje no se consumirá inmediatamente después de ingresar a la cola, sino que se consumirá solo después de alcanzar el tiempo especificado.

necesidad:

  1. Después de realizar el pedido, si el pago no se realiza dentro de los 30 minutos, el pedido se cancelará y el inventario se revertirá.

  2. 7 días después del registro exitoso de un nuevo usuario, envíe un mensaje de texto de saludo.

Método para realizar:

  1. Temporizador: bajo rendimiento --- Las consultas de la base de datos se realizan a intervalos regulares.

  2. cola de retraso

La función de la cola de retraso se completa a través de la cola de mensajes:

  • La función de cola de retraso no se proporciona en RabbitMQ.
  • Pero puede usar: TTL + combinación de cola de mensajes fallidos para lograr el efecto de la cola de retraso.

 

Manifestación:

la cola está vacía

 

Crear un proyecto springboot

archivo de configuración


#rabbitmq的配置
spring.rabbitmq.host=192.168.75.129
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/

(1) Introducir dependencias

<dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>
        <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>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

(2) Crear OrderController.java para simular pedidos

package com.wqg.controller;

import com.alibaba.fastjson.JSON;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.UUID;


/**
 * @ fileName:OrderController
 * @ description:订单
 * @ author:wqg
 * @ createTime:2023/7/13 16:24
 */
@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/saveOrder")
    public String save(Integer pid, Integer num) {
        //生成一个订单号
        String orderId = UUID.randomUUID().toString().replace("-", "");
        System.out.println("下单成功,订单号为===" + orderId);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("orderId", orderId);
        map.put("pid", pid);
        map.put("num", num);
        rabbitTemplate.convertAndSend("pt_exchange", "qy165.aaa", JSON.toJSONString(map));
        return "下单成功";
    }
}

(3) Crear MyListener.java para simular la escucha

package com.wqg.listener;

import com.alibaba.fastjson.JSON;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.HashMap;

/**
 * @ fileName:MyListener
 * @ description:
 * @ author:wqg
 * @ createTime:2023/7/13 16:35
 */
@Component
public class MyListener {

    @RabbitListener(queues = {"dead_queue"})
    public void hello(Message message){
        byte[] body = message.getBody();
        String s = new String(body);
        HashMap hashMap = JSON.parseObject(s, HashMap.class);
        System.out.println("message==="+hashMap);

        //取消订单
        System.out.println("取消订单号为==="+hashMap.get("orderId"));


    }
}

prueba

consola

 

 

3. Cómo evitar que los consumidores consuman mensajes repetidamente

La idempotencia del mensaje---no importa cuantas veces se realice la operación, el resultado es el mismo.

  • Genere una identificación global, guárdela en redis o en la base de datos y verifique si el mensaje se ha consumido antes de que el consumidor consuma el mensaje.
  • Si el mensaje se ha consumido, dígale a mq que el mensaje se ha consumido y deséchelo (reconocimiento manual).
  • Si no se ha consumido, consuma el mensaje y escriba el registro de consumo en redis o en la base de datos.

 Describa brevemente los requisitos.Si se completa el pedido, los puntos deben acumularse para el usuario y es necesario asegurarse de que los puntos no se acumulen repetidamente. Luego, antes de que mq consuma el mensaje, primero vaya a la base de datos para verificar si el mensaje se ha consumido y, si se ha consumido, luego descarte el mensaje directamente. 

Manifestación:

productor

import com.alibaba.fastjson.JSONObject;
import com.xiaojie.score.entity.Score;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
 
import java.util.UUID;
 
/**
 * @author 
 * @version 1.0
 * @description:发送积分消息的生产者
 * @date
 */
@Component
@Slf4j
public class ScoreProducer implements RabbitTemplate.ConfirmCallback {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    //定义交换机
    private static final String SCORE_EXCHANGE = "ykq_score_exchaneg";
    //定义路由键
    private static final String SCORE_ROUTINNGKEY = "score.add";
 
    /**
     * @description: 订单完成
     * @param:
     * @return: java.lang.String
     * @author xiaojie
     * @date: 
     */
    public String completeOrder() {
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单已完成");
        //发送积分通知
        Score score = new Score();
        score.setScore(100);
        score.setOrderId(orderId);
        String jsonMSg = JSONObject.toJSONString(score);
        sendScoreMsg(jsonMSg, orderId);
        return orderId;
    }
 
    /**
     * @description: 发送积分消息
     * @param:
     * @param: message
     * @param: orderId
     * @return: void
     * @author 
     * @date:
     */
 
    @Async
    public void sendScoreMsg(String jsonMSg, String orderId) {
        this.rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.convertAndSend(SCORE_EXCHANGE, SCORE_ROUTINNGKEY, jsonMSg, message -> {
            //设置消息的id为唯一
            message.getMessageProperties().setMessageId(orderId);
            return message;
        });
    }
 
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String s) {
        if (ack) {
            log.info(">>>>>>>>消息发送成功:correlationData:{},ack:{},s:{}", correlationData, ack, s);
        } else {
            log.info(">>>>>>>消息发送失败{}", ack);
        }
    }
}

consumidor

import com.alibaba.fastjson.JSONObject;
import com.rabbitmq.client.Channel;
import com.xiaojie.score.entity.Score;
import com.xiaojie.score.mapper.ScoreMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.stereotype.Component;
 
import java.io.IOException;
import java.util.Map;
 
/**
 * @author 
 * @version 1.0
 * @description: 积分的消费者
 * @date 
 */
@Component
@Slf4j
public class ScoreConsumer {
    @Autowired
    private ScoreMapper scoreMapper;
 
    @RabbitListener(queues = {"ykq_score_queue"})
    public void onMessage(Message message, @Headers Map<String, Object> headers, Channel channel) throws IOException {
        String orderId = message.getMessageProperties().getMessageId();
        if (StringUtils.isBlank(orderId)) {
            return;
        }
        log.info(">>>>>>>>消息id是:{}", orderId);
        String msg = new String(message.getBody());
        Score score = JSONObject.parseObject(msg, Score.class);
        if (score == null) {
            return;
        }
        //执行前去数据库查询,是否存在该数据,存在说明已经消费成功,不存在就去添加数据,添加成功丢弃消息
        Score dbScore = scoreMapper.selectByOrderId(orderId);
        if (dbScore != null) {
            //证明已经消费消息,告诉mq已经消费,丢弃消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            return;
        }
        Integer result = scoreMapper.save(score);
        if (result > 0) {
            //积分已经累加,删除消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            return;
        } else {
            log.info("消费失败,采取相应的人工补偿");
        } 
    }
}

Supongo que te gusta

Origin blog.csdn.net/WQGuang/article/details/131707506
Recomendado
Clasificación