Pila de tecnología de microservicios SpringCloud Seguimiento de Dark Horse (12)

Pila de tecnología de microservicio SpringCloud Seguimiento de Dark Horse 12

el objetivo de hoy

inserte la descripción de la imagen aquí

Servicio de comunicación asíncrona - Avanzado

Durante el uso de colas de mensajes, hay muchos problemas prácticos que deben tenerse en cuenta:
inserte la descripción de la imagen aquí

1. Confiabilidad del mensaje

Desde el envío de un mensaje hasta su recepción por parte de un consumidor, habrá múltiples procesos:
inserte la descripción de la imagen aquí
cada paso puede causar la pérdida del mensaje. Las razones comunes de pérdida incluyen:

  • Perdido en el envío:
    • El mensaje enviado por el productor no fue entregado al intercambio.
    • El mensaje no llegó a la cola después de llegar al intercambio.
  • MQ está inactivo, la cola perderá el mensaje
  • El consumidor se bloquea sin consumir el mensaje después de recibir el mensaje

Para estos problemas, RabbitMQ da soluciones respectivamente:

  • Mecanismo de Confirmación del Productor
  • persistencia mq
  • Mecanismo de Confirmación del Consumidor
  • Mecanismo de reintento fallido

A continuación demostramos cada paso a través de un caso.
Primero, importe el proyecto de demostración proporcionado por los materiales previos a la clase:
inserte la descripción de la imagen aquí
la estructura del proyecto es la siguiente:
inserte la descripción de la imagen aquí
simplemente inícielo con docker

docker start mq

Para crear una cola llamada simple.queue
inserte la descripción de la imagen aquí
y luego vincular el interruptor amq.topic a la cola simple.queue creada anteriormente en el interruptor, lo configuramos manualmente.
inserte la descripción de la imagen aquí
Después de ingresar el interruptor amq.topic, vincula la cola.
inserte la descripción de la imagen aquí
Después de vincular, la figura es como sigue:
inserte la descripción de la imagen aquí

1.1 Confirmación del mensaje del productor

RabbitMQ proporciona un mecanismo de confirmación del editor para evitar la pérdida de mensajes enviados a MQ. Este mecanismo debe asignar una identificación única a cada mensaje. Después de enviar el mensaje a MQ, se devolverá un resultado al remitente que indica si el mensaje se procesó correctamente.

Hay dos formas de devolver resultados:

  • Publisher-confirm, el remitente confirma
    • El mensaje se entrega con éxito al conmutador y devuelve un acuse de recibo.
    • El mensaje no se entregó al intercambio, devuelva nack
  • editor-devolución, recibo del remitente
    • El mensaje se entregó al intercambio, pero no se enrutó a la cola. Devuelve ACK y el motivo del error de enrutamiento.

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

Aviso:
inserte la descripción de la imagen aquí

1.1.1 Modificar configuración

Primero, modifique el archivo application.yml en el servicio de publicación y agregue el siguiente contenido:

spring:
  rabbitmq:
    publisher-confirm-type: correlated
    publisher-returns: true
    template:
      mandatory: true

ilustrar:

  • publish-confirm-type: habilite la confirmación del editor, aquí admite dos tipos:
    • simple: Espere sincrónicamente el resultado de la confirmación hasta que se agote el tiempo de espera
    • correlated⭐: Devolución de llamada asincrónica, defina ConfirmCallback, esta ConfirmCallback se devolverá cuando MQ devuelva el resultado
  • publish-returns: habilite la función de devolución de publicación, que también se basa en el mecanismo de devolución de llamada, pero define la función ReturnCallback
  • template.mandatory: define la política cuando falla el enrutamiento de mensajes. verdadero, llamar a ReturnCallback; falso: descartar el mensaje directamente

1.1.2 Definir la devolución de llamada de devolución

Solo se puede configurar un ReturnCallback por RabbitTemplate, por lo que debe configurarse cuando se carga el proyecto:

Modifique el servicio de publicación y agregue uno:

package cn.itcast.mq.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
public class CommonConfig implements ApplicationContextAware {
    
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    
        // 获取RabbitTemplate
        RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
        // 设置ReturnCallback
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
    
    
            // 投递失败,记录日志
            log.info("消息发送失败,应答码{},原因{},交换机{},路由键{},消息{}",
                     replyCode, replyText, exchange, routingKey, message.toString());
            // 如果有业务需要,可以重发消息
        });
    }
}

1.1.3 Definir Confirmar devolución de llamada

ConfirmCallback se puede especificar al enviar un mensaje, ya que la lógica de cada procesamiento empresarial confirma el éxito o el fracaso no es necesariamente la misma.

En la clase cn.itcast.mq.spring.SpringAmqpTest del servicio de publicación, defina un método de prueba de unidad:

public void testSendMessage2SimpleQueue() throws InterruptedException {
    
    
    // 1.消息体
    String message = "hello, spring amqp!";
    // 2.全局唯一的消息ID,需要封装到CorrelationData中
    CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
    // 3.添加callback
    correlationData.getFuture().addCallback(
        result -> {
    
    
            if(result.isAck()){
    
    
                // 3.1.ack,消息成功
                log.debug("消息发送成功, ID:{}", correlationData.getId());
            }else{
    
    
                // 3.2.nack,消息失败
                log.error("消息发送失败, ID:{}, 原因{}",correlationData.getId(), result.getReason());
            }
        },
        ex -> log.error("消息发送异常, ID:{}, 原因{}",correlationData.getId(),ex.getMessage())
    );
    // 4.发送消息
    rabbitTemplate.convertAndSend("task.direct", "task", message, correlationData);

    // 休眠一会儿,等待ack回执
    Thread.sleep(2000);
}

Después de completar todas las configuraciones, ejecute la clase de prueba SpringAmqpTest.java, lo que significa que el mensaje se envió correctamente.
inserte la descripción de la imagen aquí
Luego, tenemos una situación en la que el mensaje no se envía. Deliberadamente completamos el nombre del conmutador.
inserte la descripción de la imagen aquí
Después de la llamada , el registro de impresión en segundo plano es el siguiente:
inserte la descripción de la imagen aquí
Luego intentamos completar el error, Eche un vistazo a la clave de enrutamiento.
inserte la descripción de la imagen aquí
El mensaje de error es el siguiente:
inserte la descripción de la imagen aquí
Después de eso, restauramos el código y nos aseguramos de que sea correcto.

Resumen:
varias situaciones para procesar la confirmación de mensajes en SpringAMQP:
● Publisher-confirm:

  • El mensaje se envía con éxito al intercambio y devuelve un acuse de recibo.
  • No se pudo enviar el mensaje, no llegó al interruptor, devolvió nack
  • Ocurrió una excepción durante el envío del mensaje y no se recibió ningún recibo

● El mensaje se envía correctamente al intercambio, pero no se enruta a la cola.

  • callReturnCallback

1.2 Persistencia del mensaje

El productor confirma que el mensaje se puede entregar a la cola de RabbitMQ, pero después de enviar el mensaje a RabbitMQ, si hay un tiempo de inactividad repentino, el mensaje también se puede perder.

Para garantizar que los mensajes se almacenen de forma segura en RabbitMQ, el mecanismo de persistencia de mensajes debe estar habilitado.

  • cambiar la persistencia
  • persistencia de la cola
  • persistencia del mensaje

1.2.1 Cambiar la persistencia

El cambio en RabbitMQ no es persistente de forma predeterminada y se perderá después de que se reinicie mq.


Reiniciamos mq por comando

docker restart mq

Luego verifique el estado de la cola y el interruptor. Por ejemplo, creamos una cola persistente.
inserte la descripción de la imagen aquí
En SpringAMQP, puede especificar la persistencia del interruptor a través del código:

@Bean
public DirectExchange simpleExchange(){
    
    
    // 三个参数:交换机名称、是否持久化、当没有queue与其绑定时是否自动删除
    return new DirectExchange("simple.direct", true, false);
}

De hecho, por defecto, los cambios declarados por Spring AMQP son persistentes.

Puede ver la marca en el interruptor persistente en la consola de RabbitMQ D:
inserte la descripción de la imagen aquí

1.2.2 Persistencia de la cola

La cola en RabbitMQ no es persistente de forma predeterminada y se perderá después de que se reinicie mq.
En SpringAMQP, la persistencia del cambio se puede especificar mediante código:

Podemos ir a la interfaz gráfica de mq para eliminar simple.queue

@Bean
public Queue simpleQueue(){
    
    
    // 使用QueueBuilder构建队列,durable就是持久化的
    return QueueBuilder.durable("simple.queue").build();
}

De hecho, por defecto, las colas declaradas por Spring AMQP son persistentes.
Puede ver la marca en la cola persistente en la consola de RabbitMQ D:
inserte la descripción de la imagen aquí

Una vez hecho esto, iniciamos ConsumerApplication.java y luego vemos la interfaz gráfica de mq.
El cambio es persistente
inserte la descripción de la imagen aquí
y la cola es persistente.
inserte la descripción de la imagen aquí

1.2.3 Persistencia de mensajes

Primero detenga el servicio al consumidor, no consuma nuestros mensajes,
hacemos clic en la cola simple.queue en la interfaz gráfica de mq, luego editamos el mensaje, hacemos clic en enviar
inserte la descripción de la imagen aquí
para ver que hay 1 mensaje,
inserte la descripción de la imagen aquí
y luego reiniciamos el mq en el estibador

docker restart mq

Luego regrese y mire la interfaz gráfica de mq, y descubra que la cola todavía está allí, pero el mensaje ya no está.
inserte la descripción de la imagen aquí

Al usar SpringAMQP para enviar mensajes, puede configurar las propiedades del mensaje (MessageProperties) y especificar el modo de entrega:

  • 1: no persistente
  • 2: Persistencia

Especificar con código java:
inserte la descripción de la imagen aquí

Por defecto, cualquier mensaje enviado por Spring AMQP es persistente, sin especificarlo.
Después de ejecutar la clase de prueba SpringAmqpTest.java, verifique la interfaz gráfica de mq
inserte la descripción de la imagen aquí
para verificar la información específica
inserte la descripción de la imagen aquí
y luego reinicie el contenedor mq de docker

docker restart mq

inserte la descripción de la imagen aquí

Nota: Los conmutadores, las colas y los mensajes creados en AMQP son persistentes de forma predeterminada
interruptor:
inserte la descripción de la imagen aquí
cola:
inserte la descripción de la imagen aquí

información:
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

1.3 Confirmación del mensaje del consumidor

RabbitMQ es un mecanismo que se quema después de leer RabbitMQ confirma que el mensaje se eliminará inmediatamente después de ser consumido por el consumidor.
RabbitMQ confirma si el consumidor ha procesado correctamente el mensaje a través del recibo del consumidor: después de que el consumidor obtiene el mensaje, debe enviar un recibo ACK a RabbitMQ para indicar que procesó el mensaje.

Imagina este escenario:

  • 1) RabbitMQ entrega mensajes a los consumidores
  • 2) Después de que el consumidor recibe el mensaje, devuelve ACK a RabbitMQ
  • 3) Mensaje de eliminación de RabbitMQ
  • 4) El consumidor está inactivo y el mensaje no ha sido procesado

De esta manera, el mensaje se pierde. Por lo tanto, el momento en que el consumidor devuelve el ACK es muy importante.

SpringAMQP permite la configuración de tres modos de confirmación:

  • manual: acuse de recibo manual, debe llamar a la API para enviar acuse de recibo después de que finalice el código comercial.
  • auto⭐: reconocimiento automático. Spring monitorea si el código de escucha es anormal. Si no hay excepción, devolverá reconocimiento; si se lanza una excepción, devolverá nack.
  • none: close ack, MQ asume que el consumidor procesará con éxito el mensaje después de recibirlo, por lo que el mensaje se eliminará inmediatamente después de la entrega

De esto podemos ver:

  • En modo ninguno, la entrega de mensajes no es confiable y puede perderse
  • El modo automático es similar al mecanismo de transacción. Cuando ocurre una excepción, devuelve nack y el mensaje se retrotrae a mq; si no hay excepción, devuelve ack
  • manual: De acuerdo con la situación comercial, juzgue cuándo atacar

En general, podemos usar el valor automático predeterminado.

1.3.1 Modo de demostración ninguno

Modifique el archivo application.yml del servicio al consumidor y agregue el siguiente contenido:

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: none # 关闭ack

Modifique el método en la clase SpringRabbitListener del servicio del consumidor para simular una excepción de procesamiento de mensajes:
Modify SpringRabbitListener.java

@RabbitListener(queues = "simple.queue")
public void listenSimpleQueue(String msg) {
    
    
    log.info("消费者接收到simple.queue的消息:【{}】", msg);
    // 模拟异常
    System.out.println(1 / 0);
    log.debug("消息处理完成!");
}

La prueba puede encontrar que cuando el procesamiento del mensaje arroja una excepción, RabbitMQ aún elimina el mensaje.
Dubug inicia Consumer
y descubre que el mensaje aún no se ha recibido, por lo que desaparece de inmediato
inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí
Es decir, aunque el consumidor haya recibido el mensaje, si el consumidor no lo ha leído, se produce un error o se produce un tiempo de inactividad, el mensaje se perderá.

1.3.2 Modo automático de demostración

Cambie el mecanismo de confirmación a automático nuevamente:

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: auto # 关闭ack

Vamos a la interfaz gráfica de mq para crear un mensaje.Después
inserte la descripción de la imagen aquí
de enviarlo, vemos que hay un mensaje en la interfaz gráfica.Porque
inserte la descripción de la imagen aquí
pensamos que hemos escrito una operación aritmética 1/0 incorrecta en el fondo de IDEA, IDEA sigue reenviando la solicitud para reintentar el push del mensaje, que obviamente no cumple con nuestros requisitos
inserte la descripción de la imagen aquí

Rompe el punto en la posición de excepción y envía el mensaje de nuevo. Cuando el programa se atasca en el punto de interrupción, puede encontrar que el estado del mensaje no es correcto (estado indeterminado): después de que se lanza la excepción, porque Spring automáticamente devolverá nack,
inserte la descripción de la imagen aquí
el el mensaje volverá al estado Listo y RabbitMQ no lo eliminó:
inserte la descripción de la imagen aquí

1.4 Mecanismo de reintento por fallo de consumo

Cuando el consumidor tiene una excepción, el mensaje continuará volviendo a la cola (volver a ingresar a la cola) a la cola, y luego se volverá a enviar al consumidor, y luego la excepción nuevamente, volverá a la cola, bucle infinito, lo que hará que el procesamiento del mensaje de mq a elevarse, ejerciendo una presión innecesaria: ¿
inserte la descripción de la imagen aquí
Cómo hacerlo?

1.4.1 Reintento local

Podemos usar el mecanismo de reintento de Spring para usar el reintento local cuando ocurre una excepción en el consumidor, en lugar de una reposición ilimitada a la cola mq.

Modifique el archivo application.yml del servicio al consumidor y agregue el contenido:

spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true # 开启消费者失败重试
          initial-interval: 1000 # 初始的失败等待时长为1秒
          multiplier: 1 # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval
          max-attempts: 4 # 最大重试次数
          stateless: true # true无状态;false有状态。如果业务中包含事务,这里改为false

Modifique SpringRabbitListener.java
a la forma de impresión de registro

    @RabbitListener(queues = "simple.queue")
    public void listenSimpleQueue(String msg) {
    
    
        log.debug("消费者接收到simple.queue的消息:【" + msg + "】");
        System.out.println(1 / 0);
        log.info("消费者处理消息成功!");
    }

Reinicie el servicio al consumidor y repita la prueba anterior. Se puede encontrar:
inserte la descripción de la imagen aquí

  • SpringAMQP lanza una excepción después de 4 reintentos

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

AmqpRejectAndDontRequeueException, que indica que el reintento local desencadena

  • Verifique la consola de RabbitMQ y descubra que el mensaje se eliminó, lo que indica que SpringAMQP devolvió el reconocimiento al final y mq eliminó el mensaje.

en conclusión:

  • Cuando el reintento local está habilitado, si se lanza una excepción durante el procesamiento del mensaje, no se solicitará a la cola, pero el consumidor volverá a intentarlo localmente.
  • Después de alcanzar el número máximo de reintentos, Spring devolverá un acuse de recibo y el mensaje se descartará.

1.4.2 Estrategia de falla

En la prueba anterior, luego de alcanzar el número máximo de reintentos, el mensaje será descartado, lo cual es determinado por el mecanismo interno de Spring.

Después de activar el modo de reintento, se agota el número de reintentos. Si el mensaje aún falla, se requiere la interfaz MessageRecovery para manejarlo. Contiene tres implementaciones diferentes:

  • RejectAndDontRequeueRecoverer: después de que se agoten los reintentos, rechace y descarte directamente el mensaje. Este es el valor predeterminado

  • ImmediateRequeueMessageRecoverer: después de que se agotan los reintentos, se devuelve nack y el mensaje se vuelve a poner en cola

  • RepublishMessageRecoverer: después de que se agoten los reintentos, entregue el mensaje de error al intercambio especificado⭐
    inserte la descripción de la imagen aquí

Una solución más elegante es RepublishMessageRecoverer.Después de una falla, el mensaje se entregará a una cola designada dedicada a almacenar mensajes de excepción, y el procesamiento posterior se realizará manualmente.

1) Defina el interruptor y la cola para procesar mensajes fallidos en el servicio al consumidor

@Bean
public DirectExchange errorMessageExchange(){
    
    
    return new DirectExchange("error.direct");
}
@Bean
public Queue errorQueue(){
    
    
    return new Queue("error.queue", true);
}
@Bean
public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){
    
    
    return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error");
}

2) Definir un RepublishMessageRecoverer, colas asociadas y conmutadores

@Bean
public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
    
    
    return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
}

Código completo:

package cn.itcast.mq.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.retry.MessageRecoverer;
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
import org.springframework.context.annotation.Bean;

@Configuration
public class ErrorMessageConfig {
    
    
    @Bean
    public DirectExchange errorMessageExchange(){
    
    
        return new DirectExchange("error.direct");
    }
    @Bean
    public Queue errorQueue(){
    
    
        return new Queue("error.queue", true);
    }
    @Bean
    public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){
    
    
        return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error");
    }

    @Bean
    public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
    
    
        return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
    }
}

Una vez completada la configuración anterior, repetimos los pasos para enviar el mensaje.Después del
inserte la descripción de la imagen aquí
envío, vemos que el interruptor fallido tiene
inserte la descripción de la imagen aquí
una cola y
inserte la descripción de la imagen aquí
miramos el fondo de IDEA.Mira
inserte la descripción de la imagen aquí
los mensajes en error.queue.Está muy claro que se genera la pila de errores.
inserte la descripción de la imagen aquí

1.5 Resumen

¿Cómo garantizar la confiabilidad de los mensajes de RabbitMQ?

  • Habilite el mecanismo de confirmación del productor para garantizar que el mensaje del productor pueda llegar a la cola
  • Habilite la función de persistencia para garantizar que el mensaje no se pierda en la cola antes de consumirse
  • Encienda el mecanismo de confirmación del consumidor como automático, y el resorte completará el reconocimiento después de confirmar que el mensaje se procesó correctamente.
  • Habilite el mecanismo de reintento de falla del consumidor y establezca MessageRecoverer.Después de que fallan varios reintentos, el mensaje se entregará al conmutador anómalo y se transferirá al procesamiento manual.

2. Interruptor de letra muerta

2.1 Conociendo los interruptores de mensajes fallidos

2.1.1 ¿Qué es un interruptor de letra muerta?

¿Qué es letra muerta?

Cuando un mensaje en una cola cumple una de las siguientes condiciones, puede convertirse en letra muerta:

  • El consumidor usa basic.reject o basic.nack para declarar el error de consumo, y el parámetro de reposición en cola del mensaje se establece en falso.
  • El mensaje es un mensaje caducado, nadie consume después del tiempo de espera
  • El mensaje de la cola que se va a entregar está lleno y no se puede entregar

Si la cola que contiene mensajes fallidos está configurada con dead-letter-exchangeatributos y se especifica un conmutador, los mensajes fallidos en la cola se entregarán a este conmutador, y este conmutador se denomina intercambio de mensajes fallidos (Dead Letter Exchange, verifique DLX).

Como se muestra en la figura, el consumidor rechaza un mensaje y se convierte en mensaje fallido:
inserte la descripción de la imagen aquí
debido a que simple.queue está vinculado al intercambio de mensajes fallidos dl.direct, el mensaje fallido se entregará a este intercambio:
inserte la descripción de la imagen aquí

Si el interruptor de mensajes fallidos también está vinculado a una cola, el mensaje eventualmente ingresará a la cola donde se almacenan los mensajes fallidos:
inserte la descripción de la imagen aquí

Además, cuando la cola entrega la letra muerta al intercambio de letra muerta, debe conocer dos datos:

  • nombre de cambio de letra muerta
  • La RoutingKey vinculada al intercambio de mensajes fallidos y la cola de mensajes fallidos

Solo de esta forma podemos asegurarnos de que el mensaje entregado pueda llegar al intercambio de mensajes fallidos y ser enrutado correctamente a la cola de mensajes fallidos.
inserte la descripción de la imagen aquí

2.1.2 Uso del intercambio de mensajes fallidos para recibir mensajes fallidos (extensión)

En la estrategia de reintento fallido, el RejectAndDontRequeueRecoverer predeterminado enviará un rechazo a RabbitMQ después de que se agote el número de reintentos locales, y el mensaje se convierte en letra muerta y se descarta.

Podemos agregar un interruptor de mensajes fallidos a simple.queue y vincular una cola al interruptor de mensajes fallidos. De esta forma, el mensaje no se descartará una vez que se convierta en mensaje fallido, sino que finalmente se entregará al intercambio de mensajes fallidos y se enrutará a la cola vinculada al intercambio de mensajes fallidos.
inserte la descripción de la imagen aquí

En el servicio al consumidor, definimos un conjunto de conmutadores de mensajes fallidos y colas de mensajes fallidos:

// 声明普通的 simple.queue队列,并且为其指定死信交换机:dl.direct
@Bean
public Queue simpleQueue2(){
    
    
    return QueueBuilder.durable("simple.queue") // 指定队列名称,并持久化
        .deadLetterExchange("dl.direct") // 指定死信交换机
        .build();
}
// 声明死信交换机 dl.direct
@Bean
public DirectExchange dlExchange(){
    
    
    return new DirectExchange("dl.direct", true, false);
}
// 声明存储死信的队列 dl.queue
@Bean
public Queue dlQueue(){
    
    
    return new Queue("dl.queue", true);
}
// 将死信队列 与 死信交换机绑定
@Bean
public Binding dlBinding(){
    
    
    return BindingBuilder.bind(dlQueue()).to(dlExchange()).with("simple");
}

2.1.3 Resumen

¿Qué tipo de mensaje se convierte en letra muerta?

  • El mensaje es rechazado por el consumidor o devuelve nack
  • Mensaje agotado y no consumido
  • la cola está llena

¿Cuál es el escenario de uso del intercambio de mensajes fallidos?

  • Si la cola está vinculada a un intercambio de mensajes fallidos, el mensaje fallido se entregará al intercambio de mensajes fallidos;
  • El conmutador de mensajes fallidos se puede utilizar para recopilar todos los mensajes (mensajes fallidos) que los consumidores no pueden procesar y transferirlos al procesamiento manual para mejorar aún más la confiabilidad de la cola de mensajes.

2.2.TTL

Si un mensaje en una cola no se consume después de un tiempo de espera, se convertirá en letra muerta. Hay dos casos de tiempo de espera:

  • La cola donde se encuentra el mensaje tiene un tiempo de espera establecido
  • El mensaje en sí establece un tiempo de espera
    inserte la descripción de la imagen aquí

2.2.1 Interruptor de letra muerta que recibe letra muerta de tiempo de espera

En el SpringRabbitListener del servicio del consumidor, defina un nuevo consumidor y declare el interruptor de mensajes fallidos y la cola de mensajes fallidos:

@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "dl.ttl.queue", durable = "true"),
    exchange = @Exchange(name = "dl.ttl.direct"),
    key = "ttl"
))
public void listenDlQueue(String msg){
    
    
    log.info("接收到 dl.ttl.queue的延迟消息:{}", msg);
}

2.2.2 Declarar una cola y especificar TTL

Para establecer un tiempo de espera para una cola, debe configurar el atributo x-message-ttl al declarar la cola:

@Bean
public Queue ttlQueue(){
    
    
    return QueueBuilder.durable("ttl.queue") // 指定队列名称,并持久化
        .ttl(10000) // 设置队列的超时时间,10秒
        .deadLetterExchange("dl.ttl.direct") // 指定死信交换机
        .build();
}

Tenga en cuenta que esta cola establece el interruptor de mensajes fallidos endl.ttl.direct

Declare el interruptor y vincule el ttl al interruptor:

@Bean
public DirectExchange ttlExchange(){
    
    
    return new DirectExchange("ttl.direct");
}
@Bean
public Binding ttlBinding(){
    
    
    return BindingBuilder.bind(ttlQueue()).to(ttlExchange()).with("ttl");
}

Envía un mensaje, pero no especifiques un TTL:

@Test
public void testTTLQueue() {
    
    
    // 创建消息
    String message = "hello, ttl queue";
    // 消息ID,需要封装到CorrelationData中
    CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
    // 发送消息
    rabbitTemplate.convertAndSend("ttl.direct", "ttl", message, correlationData);
    // 记录日志
    log.debug("发送消息成功");
}

Registro de mensaje enviado:
inserte la descripción de la imagen aquí

Ver el registro del mensaje recibido:
inserte la descripción de la imagen aquí

Porque el valor TTL de la cola es 10000ms, que son 10 segundos. Puede ver que la diferencia de tiempo entre el envío y la recepción del mensaje es exactamente de 10 segundos.

2.2.3 Al enviar un mensaje, configure TTL

Al enviar un mensaje, también es posible especificar un TTL:

@Test
public void testTTLMsg() {
    
    
    // 创建消息
    Message message = MessageBuilder
        .withBody("hello, ttl message".getBytes(StandardCharsets.UTF_8))
        .setExpiration("5000")
        .build();
    // 消息ID,需要封装到CorrelationData中
    CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
    // 发送消息
    rabbitTemplate.convertAndSend("ttl.direct", "ttl", message, correlationData);
    log.debug("发送消息成功");
}

Ver registro de mensajes enviados:
inserte la descripción de la imagen aquí

Recibir registro de mensajes:
inserte la descripción de la imagen aquí

Esta vez, el retraso entre el envío y la recepción fue de solo 5 segundos.Tenga en cuenta que cuando se establece TTL tanto para la cola como para el mensaje, cualquier mensaje caducado se convertirá en letra muerta.

2.2.4 Resumen

¿Cuáles son las dos formas de tiempo de espera del mensaje?

  • Establezca el atributo ttl para la cola, y los mensajes que excedan el tiempo ttl después de ingresar a la cola se convertirán en mensajes fallidos
  • Establezca el atributo ttl para el mensaje, y la cola se convertirá en una letra muerta después de recibir el mensaje que exceda el tiempo ttl

¿Cómo darse cuenta de que el consumidor recibe el mensaje 20 segundos después de enviar un mensaje?

  • Especificar un intercambio de mensajes fallidos para la cola de destino del mensaje
  • Vincular la cola escuchada por el consumidor al intercambio de mensajes fallidos
  • Al enviar un mensaje, establezca el período de tiempo de espera para el mensaje en 20 segundos

2.3 Cola de retraso

Al usar TTL combinado con cambios de mensajes fallidos, nos damos cuenta del efecto de que los consumidores retrasan la recepción de mensajes después de enviarlos. Este modo de mensaje se denomina modo de cola de retraso (Delay Queue).

Los casos de uso para las colas de retraso incluyen:

  • Retraso en el envío de SMS
  • El usuario realiza un pedido, si el usuario no paga en 15 minutos, se cancelará automáticamente
  • Programe una reunión de trabajo y notifique automáticamente a todos los participantes 20 minutos después

Debido a que hay tantas demandas de colas de retraso, RabbitMQ lanzó oficialmente un complemento que admite de forma nativa los efectos de cola de retraso.

Este complemento es el complemento DelayExchange. Consulte la página de la lista de complementos de RabbitMQ: https://www.rabbitmq.com/community-plugins.html
inserte la descripción de la imagen aquí

Para su uso, consulte la dirección del sitio web oficial: https://blog.rabbitmq.com/posts/2015/04/scheduling-messages-with-rabbitmq

2.3.1 Instalar el complemento DelayExchange

Materiales de referencia previos a la clase:
inserte la descripción de la imagen aquí

1. Instale el complemento DelayExchange

La dirección de la guía de instalación oficial es: https://blog.rabbitmq.com/posts/2015/04/scheduling-messages-with-rabbitmq

El documento anterior se basa en la instalación nativa de RabbitMQ en Linux y luego en la instalación del complemento.

Debido a que instalamos RabbitMQ basado en Docker antes, explicaremos cómo instalar el complemento RabbitMQ basado en Docker.

2. Descarga el complemento

RabbitMQ tiene una comunidad de complementos oficial en: https://www.rabbitmq.com/community-plugins.html

Contiene varios complementos, incluido el complemento DelayExchange que vamos a usar:

inserte la descripción de la imagen aquí

Puede ir a la página de GitHub correspondiente para descargar la versión 3.8.9 del complemento, la dirección es https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/tag/3.8.9 This corresponde a RabbitMQ 3.8.5 y versiones superiores.

Los materiales previos a la clase también proporcionan complementos descargados:
inserte la descripción de la imagen aquí

3. Subir complemento

Debido a que estamos instalando en base a Docker, primero debemos verificar el volumen de datos correspondiente al directorio del complemento RabbitMQ. Si no está basado en Docker, consulte el primer capítulo para recrear el contenedor de Docker.

El nombre del volumen de datos de RabbitMQ que configuramos antes es mq-plugins, por lo que usamos el siguiente comando para ver el volumen de datos:

docker volume inspect mq-plugins

Se pueden obtener los siguientes resultados:

inserte la descripción de la imagen aquí
A continuación, cargue el complemento en este directorio:
inserte la descripción de la imagen aquí

4. Instala el complemento

Finalmente, está instalado y debe ingresar al interior del contenedor MQ para realizar la instalación. Mi contenedor se llama mq, así que ejecuta el siguiente comando:

docker exec -it mq bash

Al ejecutar -it, mqreemplace lo siguiente con su propio nombre de contenedor.

Después de ingresar al contenedor, ejecute el siguiente comando para habilitar el complemento:

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

El resultado es el siguiente:
inserte la descripción de la imagen aquí

2.3.2 Principio de Intercambio Retrasado

DelayExchange requiere que un intercambio sea declarado como retrasado. Cuando enviamos un mensaje a delayExchange, el flujo es el siguiente:

inserte la descripción de la imagen aquí

  • recibir mensaje
  • Determinar si el mensaje tiene el atributo x-delay
  • Si hay un atributo de retraso x, significa que es un mensaje retrasado, que se conserva en el disco duro, y el valor de retraso x se lee como el tiempo de retraso.
  • Devolver el resultado de enrutamiento no encontrado al remitente del mensaje
  • Después de que expire el tiempo de retraso x, vuelva a enviar el mensaje a la cola especificada

2.3.3 Uso de DelayExchange

El uso del complemento también es muy simple: declare un interruptor, el tipo del interruptor puede ser cualquier tipo, simplemente establezca el atributo retrasado en verdadero y luego declare la cola para vincularlo.

1) Declarar el interruptor DelayExchange

Basado en anotaciones (recomendado):
inserte la descripción de la imagen aquí
SpringRabbitListener.java

/**
     * 延迟交换机和队列
     *
     * @param message
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "delay.queue", durable = "true"),
            exchange = @Exchange(name = "delay.direct", delayed = "true"),
            key = "delay"
    ))
    public void listenDelayExchange(String message) {
    
    
        log.info("消费者接收到了delay.queue的消息" + message);
    }

También se puede basar en @Bean:
inserte la descripción de la imagen aquí

2) Enviar un mensaje

Al enviar un mensaje, asegúrese de llevar el atributo x-delay para especificar el tiempo de retraso:
inserte la descripción de la imagen aquí
SpringAmqpTest.java

    /**
     * 发送延迟消息
     *
     * @throws InterruptedException
     */
    @Test
    public void testSendDealyMessage() throws InterruptedException {
    
    
        String routingKey = "delay";

        // 创建消息
        Message message = MessageBuilder.withBody("hello, delay message !".getBytes(StandardCharsets.UTF_8))
                .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
                .setHeader("x-delay", 5000)
                .build();
        // 准备CorrelationData
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        rabbitTemplate.convertAndSend("delay.direct", routingKey, message, correlationData);
    }
    log.info("发送delay消息成功!");

Eche un vistazo a la interfaz gráfica de mq
inserte la descripción de la imagen aquí

Ejecute la clase de prueba SpringAmqpTest.java para enviar un mensaje. Aunque el mensaje de demora se envió correctamente, a continuación se informa un error.
inserte la descripción de la imagen aquí
Verifique al consumidor y el mensaje se recibe correctamente después de 5 segundos.
inserte la descripción de la imagen aquí

Entonces, ¿por qué hay un error aquí? Esto se debe a que el interruptor de retardo envía el mensaje después de retener el mensaje durante 5 segundos. Este no es un informe de error, pero el mensaje se almacena temporalmente durante 5 segundos y se envía a la cola después de 5 segundos. Luego, ¿Podemos evitar que informen errores? Por supuesto, podemos seguir modificando. Juzgando si reenviar
de acuerdo a si hay un valor, o no reenviar si hay un valor Modifique CommonConfig.java en el publicadorreceivedDelay

inserte la descripción de la imagen aquí

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    
    
        // 获取RabbitTemplate对象
        RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
        // 配置eturnCallback
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
    
    
            // 判断是否是延迟消息
            if (message.getMessageProperties().getReceivedDelay() > 0) {
    
    
                // 是一个延迟消息,忽略报错信息
                return;
            }
            // 记录日志
            log.error("消息发送到队列失败,响应码:{},失败原因:{},交换机:{},routingKey:{},消息:{}",
                    replyCode, replyText, exchange, routingKey, message);
            // 如果有失败的,可以进行消息的重发

        });
    }

Luego envíe el mensaje a través de la clase de prueba y el error desaparecerá.
inserte la descripción de la imagen aquí

2.3.4 Resumen

¿Cuáles son los pasos para usar el complemento de cola de demora?

• Declarar un cambio y agregar el atributo retrasado a verdadero

• Al enviar un mensaje, agregue el encabezado x-delay, el valor es el tiempo de espera

3. Cola perezosa

3.1 Problema de acumulación de mensajes

Cuando la velocidad a la que los productores envían mensajes supera la velocidad a la que los consumidores pueden procesar los mensajes, los mensajes en la cola se acumularán hasta que la cola almacene mensajes hasta el límite superior. Los mensajes enviados más tarde se convertirán en mensajes fallidos y pueden descartarse.Este es el problema de la acumulación de mensajes .

inserte la descripción de la imagen aquí
Hay tres formas de resolver la acumulación de mensajes:

  • Agregue más consumidores y aumente la velocidad de consumo. Ese es el modo de cola de trabajo que dijimos antes
  • Abra el grupo de subprocesos en el consumidor para acelerar el procesamiento de mensajes
  • Expanda el volumen de la cola y aumente el límite de apilamiento

Para aumentar la capacidad de la cola, obviamente no es posible almacenar mensajes en la memoria.

3.2 Cola perezosa

A partir de la versión 3.6.0 de RabbitMQ, se ha agregado el concepto de Lazy Queues, es decir, colas perezosas . Las características de la cola perezosa son las siguientes:

  • Después de recibir el mensaje, guárdelo directamente en el disco en lugar de la memoria
  • Los consumidores solo leen del disco y los cargan en la memoria cuando quieren consumir mensajes
  • Admite millones de almacenamiento de mensajes

3.2.1 Establecer lazy-queue basado en la línea de comando

Para configurar una cola como cola perezosa, solo necesita especificar el atributo x-queue-mode como perezoso al declarar la cola. Una cola en ejecución se puede cambiar a una cola perezosa a través de la línea de comando:

rabbitmqctl set_policy Lazy "^lazy-queue$" '{"queue-mode":"lazy"}' --apply-to queues  

Interpretación de comandos:

  • rabbitmqctl: herramienta de línea de comandos para RabbitMQ
  • set_policy: agregar una estrategia
  • Lazy: Nombre de la póliza, que se puede personalizar
  • "^lazy-queue$": Haga coincidir el nombre de la cola con una expresión regular
  • '{"queue-mode":"lazy"}': establece el modo de cola en modo perezoso
  • --apply-to queues : El objetivo de la política son todas las colas

3.2.2 Declarar lazy-queue basado en @Bean

inserte la descripción de la imagen aquí
Nueva clase LazyConfig.java

@Configuration
public class LazyConfig {
    
    
    /**
     * 惰性队列
     *
     * @return
     */
    @Bean
    public Queue lazyQueue() {
    
    
        return QueueBuilder.durable("lazy.queue").lazy().build();
    }

    /**
     * 普通队列
     *
     * @return
     */
    @Bean
    public Queue normalQueue() {
    
    
        return QueueBuilder.durable("normal.queue").build();
    }
}

Inicie el servicio al consumidor y vea la interfaz gráfica de mq
inserte la descripción de la imagen aquí
Modifique SpringAmqpTest.java

    /**
     * 测试惰性队列
     *
     * @throws InterruptedException
     */
    @Test
    public void testSendLazyQueue() throws InterruptedException {
    
    
        for (int i = 0; i < 1000000; i++) {
    
    
            String routingKey = "lazy.queue";

            // 创建消息
            Message message = MessageBuilder.withBody("hello, lazy queue".getBytes(StandardCharsets.UTF_8))
                    .setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT)
                    .build();
            rabbitTemplate.convertAndSend(routingKey, message);
            //log.info("发送lazy队列消息成功!");
        }
    }

    /**
     * 测试惰性队列
     *
     * @throws InterruptedException
     */
    @Test
    public void testSendNormalQueue() throws InterruptedException {
    
    
        for (int i = 0; i < 1000000; i++) {
    
    
            String routingKey = "normal.queue";

            // 创建消息
            Message message = MessageBuilder.withBody("hello, normal queue".getBytes(StandardCharsets.UTF_8))
                    .setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT)
                    .build();
            rabbitTemplate.convertAndSend(routingKey, message);
            //log.info("发送normal队列消息成功!");
        }
    }

Vemos que en la cola perezosa, no está en la memoria, y se escribe directamente en el disco, que es relativamente estable.Vemos
inserte la descripción de la imagen aquí
que en la cola normal, se escribe en la memoria, y después de escribir por un tiempo , parte de él se actualizará en el disco, que no es estable.
inserte la descripción de la imagen aquí

3.2.3 Declarar LazyQueue basado en @RabbitListener

inserte la descripción de la imagen aquí

3.3 Resumen

¿La solución al problema de la acumulación de mensajes?

  • Vincule a varios consumidores en la cola para aumentar la velocidad de consumo
  • Usando colas perezosas, puede almacenar más mensajes en mq

¿Cuáles son las ventajas de las colas perezosas?

  • Según el almacenamiento en disco, el límite superior de mensajes es alto
  • No hay salida de página intermitente y el rendimiento es relativamente estable

¿Cuáles son las desventajas de las colas perezosas?

  • Según el almacenamiento en disco, se reducirá la puntualidad de los mensajes
  • El rendimiento está limitado por la E/S del disco

4. Clúster MQ

4.1 Clasificación de conglomerados

RabbitMQ está escrito en base al lenguaje Erlang, y Erlang es un lenguaje orientado a la concurrencia que, naturalmente, admite el modo de clúster. El clúster RabbitMQ tiene dos modos:

Clúster ordinario : Es un clúster distribuido, que distribuye las colas a cada nodo del clúster, mejorando así la capacidad de concurrencia de todo el clúster.

Clúster espejo : Es un clúster maestro-esclavo.Sobre la base de los clústeres ordinarios, se agrega una función de copia de seguridad maestro-esclavo para mejorar la disponibilidad de datos del clúster.

Aunque el clúster espejo admite maestro-esclavo, la sincronización maestro-esclavo no es muy consistente y puede haber riesgo de pérdida de datos en algunos casos. Por lo tanto, después de la versión 3.8 de RabbitMQ, se introdujo una nueva función: la cola de arbitraje para reemplazar el clúster espejo, y la capa inferior usa el protocolo Raft para garantizar la consistencia de los datos entre el maestro y el esclavo.

4.2 Clúster ordinario

4.2.1 Estructura y características del conglomerado

Los clusters ordinarios, o clusters clásicos, tienen las siguientes características:

  • Parte de los datos se compartirán entre cada nodo del clúster, incluidos: metadatos de cambio y cola. Los mensajes en la cola no están incluidos.
  • Al acceder a un nodo en el clúster, si la cola no está en el nodo, se pasará del nodo donde se encuentran los datos al nodo actual y se devolverá
  • Si el nodo donde se encuentra la cola se cae, los mensajes en la cola se perderán

La estructura se muestra en la figura:

inserte la descripción de la imagen aquí

4.2.2 Despliegue

Materiales previos a la clase de referencia: "RabbitMQ Deployment Guide.md"

Despliegue de clúster
A continuación, veamos cómo instalar un clúster RabbitMQ.

1. Clasificación de conglomerados

En la documentación oficial de RabbitMQ, se describen dos métodos de configuración de clúster:

  • Modo normal: los clústeres de modo normal no realizan sincronización de datos, y cada MQ tiene su propia cola e información de datos (se sincronizará otra información de metadatos, como los conmutadores). Por ejemplo, tenemos 2 MQ: mq1 y mq2.Si su mensaje está en mq1 y está conectado a mq2, entonces mq2 irá a mq1 para extraer el mensaje y devolvérselo. Si mq1 falla, el mensaje se perderá.
  • Modo espejo: a diferencia del modo normal, la cola se sincronizará entre los nodos espejo de cada mq, para que pueda recibir el mensaje cuando se conecte a cualquier nodo espejo. Y si un nodo deja de funcionar, no se perderán datos. Sin embargo, este enfoque aumenta el consumo de ancho de banda para la sincronización de datos.

Veamos primero el clúster de modo normal. Nuestro plan es implementar un clúster mq de 3 nodos:

nombre de la CPU puerto de consola puerto de comunicación amqp
mq1 8081 —> 15672 8071 —> 5672
mq2 8082 —> 15672 8072 —> 5672
mq3 8083 —> 15672 8073 —> 5672

Las etiquetas predeterminadas de los nodos en el clúster son: rabbit@[hostname], por lo que los nombres de los tres nodos anteriores son:

  • conejo@mq1
  • conejo@mq2
  • conejo@mq3

2. Obtener galletas

La capa inferior de RabbitMQ depende de Erlang, y la máquina virtual de Erlang es un lenguaje orientado a la distribución que admite el modo de clúster de forma predeterminada. Cada nodo RabbitMQ en modo de clúster utiliza una cookie para determinar si se les permite comunicarse entre sí.

Para que dos nodos puedan comunicarse, deben tener el mismo secreto compartido, llamado cookie de Erlang . Una cookie es solo una cadena de caracteres alfanuméricos de hasta 255 caracteres.

Cada nodo del clúster debe tener la misma cookie . También es necesario entre instancias para comunicarse entre sí.

Primero obtenemos un valor de cookie en el contenedor mq iniciado previamente como la cookie de clúster. Ejecute el siguiente comando:

docker exec -it mq cat /var/lib/rabbitmq/.erlang.cookie

Puede ver el valor de la cookie de la siguiente manera:

FXZMCVGLBIXZCDEMMVZQ

A continuación, detenga y elimine el contenedor mq actual y reconstruiremos el clúster.

docker rm -f mq

inserte la descripción de la imagen aquí
volumen de datos limpio

docker volume prune

3. Preparar la configuración del clúster

Cree un nuevo archivo de configuración rabbitmq.conf en el directorio /tmp:

cd /tmp
# 创建文件
touch rabbitmq.conf

El contenido del archivo es el siguiente:

loopback_users.guest = false
listeners.tcp.default = 5672
cluster_formation.peer_discovery_backend = rabbit_peer_discovery_classic_config
cluster_formation.classic_config.nodes.1 = rabbit@mq1
cluster_formation.classic_config.nodes.2 = rabbit@mq2
cluster_formation.classic_config.nodes.3 = rabbit@mq3

Crear otro archivo para registrar cookies

cd /tmp
# 创建cookie文件
touch .erlang.cookie
# 写入cookie
echo "FXZMCVGLBIXZCDEMMVZQ" > .erlang.cookie
# 修改cookie文件的权限
chmod 600 .erlang.cookie

Prepare tres directorios, mq1, mq2, mq3:

cd /tmp
# 创建目录
mkdir mq1 mq2 mq3

Luego copie rabbitmq.conf y los archivos de cookies a mq1, mq2, mq3:

# 进入/tmp
cd /tmp
# 拷贝
cp rabbitmq.conf mq1
cp rabbitmq.conf mq2
cp rabbitmq.conf mq3
cp .erlang.cookie mq1
cp .erlang.cookie mq2
cp .erlang.cookie mq3

4. Inicie el clúster

Crear una red:

docker network create mq-net

volumen docker crear

ejecutar comando

docker run -d --net mq-net \
-v ${PWD}/mq1/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \
-v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
-e RABBITMQ_DEFAULT_USER=itcast \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq1 \
--hostname mq1 \
-p 8071:5672 \
-p 8081:15672 \
rabbitmq:3.8-management
docker run -d --net mq-net \
-v ${PWD}/mq2/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \
-v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
-e RABBITMQ_DEFAULT_USER=itcast \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq2 \
--hostname mq2 \
-p 8072:5672 \
-p 8082:15672 \
rabbitmq:3.8-management
docker run -d --net mq-net \
-v ${PWD}/mq3/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf \
-v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
-e RABBITMQ_DEFAULT_USER=itcast \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq3 \
--hostname mq3 \
-p 8073:5672 \
-p 8083:15672 \
rabbitmq:3.8-management

5. prueba

Agregue una cola al nodo mq1:
inserte la descripción de la imagen aquí

Como se muestra en la figura, también se puede ver en las dos consolas mq2 y mq3:
inserte la descripción de la imagen aquí

6. Prueba de intercambio de datos

Haga clic en esta cola para ingresar a la página de administración:
inserte la descripción de la imagen aquí

Luego use la consola para enviar un mensaje a esta cola:
inserte la descripción de la imagen aquí

Como resultado, este mensaje se puede ver en mq2 y mq3:

inserte la descripción de la imagen aquí

7. Pruebas de usabilidad

Dejamos caer uno de los nodos mq1:

docker stop mq1

Luego, inicie sesión en la consola de mq2 o mq3 y descubra que simple.queue ya no está disponible:
inserte la descripción de la imagen aquí

Significa que los datos no se copian en mq2 y mq3.

4.3 Grupo de espejos

4.3.1 Estructura y características del conglomerado

Grupo de espejos: la esencia es el modo maestro-esclavo, con las siguientes características:

  • Los conmutadores, las colas y los mensajes en las colas se respaldarán de forma síncrona entre los nodos espejo de cada mq.
  • El nodo que crea la cola se denomina nodo principal de la cola y los otros nodos en los que se realiza la copia de seguridad se denominan nodos espejo de la cola.
  • El nodo maestro de una cola puede ser el nodo espejo de otra cola
  • Todas las operaciones son completadas por el nodo maestro y luego sincronizadas con el nodo espejo
  • Después de que el maestro deje de funcionar, el nodo espejo será reemplazado por el nuevo maestro

La estructura se muestra en la figura:
inserte la descripción de la imagen aquí

4.3.2 Despliegue

Materiales previos a la clase de referencia:
modo espejo "RabbitMQ Deployment Guide.md"

En el caso de ahora, una vez que el host que creó la cola se cae, la cola no estará disponible. No tiene capacidades de alta disponibilidad. Si desea resolver este problema, debe utilizar la solución de clúster espejo oficial.

Dirección del documento oficial: https://www.rabbitmq.com/ha.html

1. Características del modo espejo

De forma predeterminada, las colas solo persisten en el nodo que creó la cola. En el modo espejo, el nodo que crea la cola se denomina nodo maestro de la cola y la cola también se copia en otros nodos del clúster, también llamado nodo espejo de la cola.

Sin embargo, se pueden crear diferentes colas en cualquier nodo del clúster, por lo que diferentes colas pueden tener diferentes nodos maestros. Incluso, el nodo maestro de una cola puede ser el nodo espejo de otra cola .

Todas las solicitudes enviadas por los usuarios a la cola, como el envío de mensajes y la recepción de mensajes, se completarán en el nodo maestro de manera predeterminada. Si la solicitud se recibe del nodo esclavo, también se enrutará al nodo maestro para su finalización. El nodo espejo sólo desempeña la función de realizar una copia de seguridad de los datos .

Cuando el nodo maestro recibe el ACK del consumidor, todos los espejos eliminan los datos del nodo.

Resumido de la siguiente manera:

  • La estructura de la cola espejo es un maestro y múltiples esclavos (el esclavo es una imagen espejo)
  • Todas las operaciones son completadas por el nodo maestro y luego sincronizadas con el nodo espejo
  • Después de que el maestro se apague, el nodo espejo lo reemplazará como el nuevo maestro (si el maestro se cae antes de que se complete la sincronización maestro-esclavo, puede ocurrir una pérdida de datos)
  • No tiene función de equilibrio de carga, porque el nodo maestro completará todas las operaciones (pero diferentes colas, el nodo maestro puede ser diferente, puede usar esto para mejorar el rendimiento)

2. Configuración del modo espejo

Hay 3 modos para la configuración del modo espejo:

modo ha ha-params Efecto
modo exacto exactamente El número de copias del recuento de la cola. El número de réplicas de cola (suma de servidores principal y reflejado) en el clúster. Si el recuento es 1, significa una sola copia: el nodo maestro de la cola. Un valor de conteo de 2 significa 2 copias: 1 maestro de cola y 1 espejo de cola. En otras palabras: cuenta = número de espejos + 1. Si hay menos de nodos en el clúster, la cola se reflejará en todos los nodos. Si hay un total de clúster mayor que count+1 y el nodo que contiene el espejo falla, se creará un nuevo espejo en otro nodo.
todo (ninguno) Las colas se reflejan en todos los nodos del clúster. La cola se reflejará en cualquier nodo recién unido. La duplicación de todos los nodos ejercerá una presión adicional sobre todos los nodos del clúster, incluida la E/S de red, la E/S de disco y el uso de espacio en disco. Se recomienda usar exactamente y establecer el número de réplicas en (N / 2 +1).
nodos nombres de nodos Especifique en qué nodos se crea la cola. Si no existe ninguno de los nodos especificados, se producirá una excepción. Si el nodo especificado existe en el clúster pero no está disponible temporalmente, se creará un nodo en el nodo al que está conectado el cliente actual.

Aquí usamos el comando rabbitmqctl como ejemplo para explicar la sintaxis de configuración.

Ejemplo de sintaxis:

3. Modo exacto

rabbitmqctl set_policy ha-two "^two\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'
  • rabbitmqctl set_policy: Redacción fija
  • ha-two: nombre de la política, personalizado
  • "^two\.": haga coincidir la expresión regular de la cola, la cola que cumpla con las reglas de nomenclatura tendrá efecto, aquí hay cualquier two.nombre de cola que comience con
  • '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}': Contenido de la política
    • "ha-mode":"exactly": modo de estrategia, aquí está exactamente el modo, especifique el número de copias
    • "ha-params":2: Parámetro de política, aquí hay 2, es decir, el número de réplicas es 2, 1 maestro y 1 espejo
    • "ha-sync-mode":"automatic": estrategia de sincronización, el valor predeterminado es manual, es decir, los nodos espejo recién agregados no sincronizarán los mensajes antiguos. Si se establece en automático, el nodo espejo recién agregado sincronizará todos los mensajes en el nodo maestro, lo que generará una sobrecarga de red adicional.

4. Modo

rabbitmqctl set_policy ha-all "^all\." '{"ha-mode":"all"}'
  • ha-all: nombre de la política, personalizado
  • "^all\.": coincide con todos all.los nombres de cola que comienzan con
  • '{"ha-mode":"all"}': contenido de la política
    • "ha-mode":"all": Modo de estrategia, aquí está todo el modo, es decir, todos los nodos se llamarán nodos espejo

modo 5.nodos

rabbitmqctl set_policy ha-nodes "^nodes\." '{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}'
  • rabbitmqctl set_policy: Redacción fija
  • ha-nodes: nombre de la política, personalizado
  • "^nodes\.": haga coincidir la expresión regular de la cola, la cola que cumpla con las reglas de nomenclatura tendrá efecto, aquí hay cualquier nodes.nombre de cola que comience con
  • '{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}': Contenido de la política
    • "ha-mode":"nodes": Modo de estrategia, aquí está el modo de nodos.
    • "ha-params":["rabbit@mq1", "rabbit@mq2"]: Parámetro de política, aquí especifique el nombre del nodo donde se encuentra la copia

6. Prueba

Usamos la duplicación en modo exacto, porque la cantidad de nodos del clúster es 3, por lo que la cantidad de duplicación se establece en 2.

Ejecute el siguiente comando:

docker exec -it mq1 rabbitmqctl set_policy ha-two "^two\." '{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'

A continuación, creamos una nueva cola:

inserte la descripción de la imagen aquí

Verifique la cola en cualquier consola mq:
inserte la descripción de la imagen aquí

7. Compartición de datos de prueba

Envía un mensaje a two.queue:
inserte la descripción de la imagen aquí

Luego verifique el mensaje en cualquier consola de mq1, mq2, mq3:

inserte la descripción de la imagen aquí

8. Pruebe la alta disponibilidad

Ahora, dejamos que el nodo maestro mq1 de two.queue baje:

docker stop mq1

Ver el estado del clúster:
inserte la descripción de la imagen aquí

Ver el estado de la cola:
inserte la descripción de la imagen aquí

¡El descubrimiento sigue siendo saludable! Y su nodo maestro cambió a rabbit@mq2

5. Cola de arbitraje

A partir de la versión 3.8 de RabbitMQ, se ha introducido una nueva cola de quórum que tiene funciones similares al equipo espejo, pero es más conveniente de usar.

4.4 Cola de arbitraje

4.4.1 Características del conglomerado

Cola de arbitraje: La cola de arbitraje es una nueva función disponible después de la versión 3.8.Se utiliza para reemplazar la cola espejo y tiene las siguientes características:

  • Al igual que la cola espejo, es un modo maestro-esclavo y admite la sincronización de datos maestro-esclavo
  • Muy fácil de usar, sin configuraciones complicadas
  • Sincronización maestro-esclavo basada en el protocolo Raft, consistencia fuerte

4.4.2 Despliegue

Materiales previos a la clase de referencia: "RabbitMQ Deployment Guide.md"

Cola de arbitraje
A partir de RabbitMQ 3.8, se ha introducido una nueva cola de arbitraje que tiene funciones similares a la cola duplicada, pero es más cómoda de usar.

1. Agregar cola de quórum

Para agregar una cola en cualquier consola, asegúrese de seleccionar el tipo de cola como Tipo de quórum.
inserte la descripción de la imagen aquí
Ver colas en cualquier consola:
inserte la descripción de la imagen aquí

Como puede ver, las palabras + 2 de la cola de quórum. Significa que esta cola tiene 2 nodos espejo.

Porque el número de espejo predeterminado de la cola de quórum es 5. Si su clúster tiene 7 nodos, entonces la cantidad de espejos debe ser 5; y nuestro clúster tiene solo 3 nodos, por lo que la cantidad de espejos es 3.

2. prueba

Puede consultar la prueba en el clúster reflejado, el efecto es el mismo.

3. Expansión del clúster

4. Únete al clúster

1) Inicie un nuevo contenedor MQ:

docker run -d --net mq-net \
-v ${PWD}/.erlang.cookie:/var/lib/rabbitmq/.erlang.cookie \
-e RABBITMQ_DEFAULT_USER=itcast \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq4 \
--hostname mq5 \
-p 8074:15672 \
-p 8084:15672 \
rabbitmq:3.8-management

2) Ingrese a la consola del contenedor:

docker exec -it mq4 bash

3) Detener el proceso mq

rabbitmqctl stop_app

4) Restablecer los datos en RabbitMQ:

rabbitmqctl reset

5) Únete a mq1:

rabbitmqctl join_cluster rabbit@mq1

6) Inicie el proceso mq nuevamente

rabbitmqctl start_app

inserte la descripción de la imagen aquí

5. Aumentar la copia de la cola de arbitraje

Primero verifiquemos la copia actual de la cola quorum.queue e ingresemos el contenedor mq1:

docker exec -it mq1 bash

Ejecutando una orden:

rabbitmq-queues quorum_status "quorum.queue"

Resultado:
inserte la descripción de la imagen aquí
Ahora, agreguemos mq4:

rabbitmq-queues add_member "quorum.queue" "rabbit@mq4"

resultado:
inserte la descripción de la imagen aquí

Ver de nuevo:

rabbitmq-queues quorum_status "quorum.queue"

inserte la descripción de la imagen aquí
Compruebe la consola y descubra que la cantidad de imágenes reflejadas de quorum.queue también ha cambiado de +2 a +3:
inserte la descripción de la imagen aquí

4.4.3 Código Java para crear una cola de arbitraje

@Bean
public Queue quorumQueue() {
    
    
    return QueueBuilder
        .durable("quorum.queue") // 持久化
        .quorum() // 仲裁队列
        .build();
}

4.4.4 SpringAMQP se conecta al clúster MQ

Tenga en cuenta que la dirección se usa aquí en lugar del host y el puerto

spring:
  rabbitmq:
    addresses: 192.168.150.105:8071, 192.168.150.105:8072, 192.168.150.105:8073
    username: itcast
    password: 123321
    virtual-host: /

Supongo que te gusta

Origin blog.csdn.net/sinat_38316216/article/details/129840089
Recomendado
Clasificación