RabbitMQ 死信交换机的详述➕应用

Welcome 的Huihui's Code World ! !

接下来看看由辉辉所写的关于RabbitMQ的相关操作吧

目录

Welcome 的Huihui's Code World ! !

一.什么是死信交换机

二. 死信队列的应用场景

三.死信队列【TTL】

1.创建主交换机和主队列

2.创建死信交换机和死信队列

3.设置主队列的死信参数

4.将主队列绑定到主交换机

5.将死信队列绑定到死信交换机

6.消费者

7.测试

四. 死信队列【Reject】

1.配置消息确认模式

⭐⭐消息消费者如何通知 Rabbit 消息消费成功?

2.消费者


一.什么是死信交换机

这个在上篇博文已经讲到了,想看的可以点击一下

简单来说,就是生产者将消息投递到 queue 里了,consumer 从 queue 取出消息进行消费,如果它一直无法消费某条数据,那么可以把这条消息放入死信队列里面。等待 条件满足了再从死信队列中取出来再次消费,从而避免消息丢失。

⭐⭐注意:死信交换机本质上就是一个普通的交换机,只是因为队列设置了参数指定了死信交换机,这个普通的交换机才成为了死信的接收者

RabbitMQ 死信交换机的详述➕应用_第1张图片

二. 死信队列的应用场景

  1. 错误处理:当消息无法被成功处理时,可以将其发送到死信队列,以便后续进行错误处理、日志记录或告警。
  2. 延迟消息:通过设置消息的过期时间,可以实现延迟消息的功能。当消息过期时,将被发送到死信队列,可以用于实现定时任务或延迟任务。
  3. 重试机制:当消息处理失败时,可以将消息发送到死信队列,并设置适当的重试策略。例如,可以使用指数退避算法对消息进行重试,以提高消息处理的成功率。
  4. 消息分析:通过监听死信队列,可以对无法被正常消费的消息进行分析和统计,以了解系统中存在的问题或异常情况

三.死信队列【TTL】

这里死信消息是设置了过期的时间【TTL】

1.创建主交换机和主队列

首先,需要创建一个主交换机和一个主队列。这些是正常消息传递的目标,当消息无法被正常消费时,它们将成为死信的来源。

// 创建一个名为queueA的队列
@Bean
public Queue queueA() {
    return new Queue("queueA");
}

// 创建一个名为ExchangeA的直接交换机
@Bean
public DirectExchange ExchangeA() {
    return new DirectExchange("ExchangeA");
}


2.创建死信交换机和死信队列

接下来,需要创建一个死信交换机和一个死信队列。这些将作为死信消息的目标。

// 创建一个名为queueB的队列
@Bean
public Queue queueB() {
    return new Queue("queueB");
}

// 创建一个名为ExchangeB的直接交换机
@Bean
public DirectExchange ExchangeB() {
    return new DirectExchange("ExchangeB");
}


3.设置主队列的死信参数

在创建主队列时,需要为其设置一些参数来定义死信的行为。具体而言,需要设置x-dead-letter-exchange参数为死信交换机的名称,以及x-dead-letter-routing-key参数为死信队列的路由键。

 // 创建一个名为queueA的队列
    @Bean
    public Queue queueA() {
        Map config = new HashMap<>();
        //message在该队列queue的存活时间最大为10秒
        config.put("x-message-ttl", 10000);
        //x-dead-letter-exchange参数是设置该队列的死信交换器(DLX)
        config.put("x-dead-letter-exchange", "ExchangeB");
        //x-dead-letter-routing-key参数是给这个DLX指定路由键
        config.put("x-dead-letter-routing-key", "BB");
        return new Queue("queueA",true,true,false,config);
    }

关于其中的参数:

RabbitMQ 死信交换机的详述➕应用_第2张图片

  • name:队列的唯一标识符,用于在消息中间件中识别特定的队列。每个队列都有一个名称,发送方将消息发送到指定的队列,接收方从队列中获取消息进行处理。
  • durable:指定队列是否需要持久化存储。如果队列被标记为持久化,那么即使在消息中间件重启之后,队列中的消192息也不会丢失。这对于重要的消息和应用场景非常关键。
  • exclusive:用于指定队列是否为独占队列。当一个队列被标记为独占时【exclusive=true】,只有创建该队列的连接或通道可以访问或使用这个队列。
  • autoDelete:用于指定队列是否在没有任何消费者订阅或连接时自动删除。当一个队列中的"autodelete"属性为true时,如果没有消费者订阅该队列或者没有生产者向该队列发送消息,那么该队列将自动被删除。

4.将主队列绑定到主交换机

将主队列与主交换机进行绑定,以确保正常消息能够被正确路由到主队列。

// 将queueA与ExchangeA进行绑定,并设置路由键为"AA"
@Bean
public Binding bindingAA() {
    return BindingBuilder
            .bind(queueA()) // 将queueA与ExchangeA进行绑定
            .to(ExchangeA()) // 指定绑定的目标交换机为ExchangeA
            .with("AA"); // 设置路由键为"AA"
}

5.将死信队列绑定到死信交换机

将死信队列与死信交换机进行绑定,以确保死信消息能够被正确路由到死信队列。

// 将queueB与ExchangeB进行绑定,并设置路由键为"BB"
@Bean
public Binding bindingBB() {
    return BindingBuilder
            .bind(queueB()) // 将queueB与ExchangeB进行绑定
            .to(ExchangeB()) // 指定绑定的目标交换机为ExchangeB
            .with("BB"); // 设置路由键为"BB"
}

6.消费者

这个消费者监听的是queueB,当queueA的消息过期之后就会交到死信交换机中的queueB进行处理

package com.example.consumer.exchange;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component // 声明这是一个Spring组件
@RabbitListener(queues = {"queueB"}) // 监听名为"queueB"的队列
public class DeadLetterReceive {

    @RabbitHandler // 当接收到消息时,调用此方法处理
    public void handler(String msg) { // 参数为接收到的消息,类型为Map
        System.out.println("QB接到消息"+msg); // 打印接收到的消息
    }
}

7.测试

先疯狂的访问queueA队列的方法

RabbitMQ 死信交换机的详述➕应用_第3张图片现在可以看到queueA这里有六条消息

RabbitMQ 死信交换机的详述➕应用_第4张图片

queueA的消息过期了之后,queueB消费者中就接收到消息了

RabbitMQ 死信交换机的详述➕应用_第5张图片

现在把queueB的消费者停掉

RabbitMQ 死信交换机的详述➕应用_第6张图片

现在再去疯狂的访问queueA的方法,此时就可以发现,queueA的消息都到queueB那里去了

RabbitMQ 死信交换机的详述➕应用_第7张图片

四. 死信队列【Reject】

这里死信消息是设置成了拒绝,【requeue 参数为 false】,还是用的上面所创建的交换机以及队列...

1.配置消息确认模式

  • 消息确认模式有:

    • AcknowledgeMode.NONE:自动确认

    • AcknowledgeMode.AUTO:根据情况确认

    • AcknowledgeMode.MANUAL:手动确认

server:
  port: 9999
spring:
  rabbitmq:
    host: 192.168.101.129
    password: 123456
    port: 5672
    username: wh
    virtual-host: my_vhost
    listener:
      simple:
        acknowledge-mode: manual

RabbitMQ 死信交换机的详述➕应用_第8张图片

消息接收确认

⭐⭐消息消费者如何通知 Rabbit 消息消费成功?

  • 消息通过 ACK 确认是否被正确接收,每个 Message 都要被确认(acknowledged),可以手动去 ACK 或自动 ACK

  • 自动确认会在消息发送给消费者后立即确认,但存在丢失消息的可能,如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息

  • 如果消息已经被处理,但后续代码抛出异常,使用 Spring 进行管理的话消费端业务逻辑会进行回滚,这也同样造成了实际意义的消息丢失

  • 如果手动确认则当消费者调用 ack、nack、reject 几种方法进行确认,手动确认可以在业务失败后进行一些操作,如果消息未被 ACK 则会发送到下一个消费者

  • 如果某个服务忘记 ACK 了,则 RabbitMQ 不会再发送数据给它,因为 RabbitMQ 认为该服务的处理能力有限

  • ACK 机制还可以起到限流作用,比如在接收到某条消息时休眠几秒钟

2.消费者

这个消费者是queueA的消费者

package com.example.consumer.exchange;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component // 声明这是一个Spring组件
@RabbitListener(queues = "queueA") // 监听名为"queueA"的队列
public class DeadLetterReceiveA {

    @RabbitHandler // 当接收到消息时,调用此方法处理
    public void handler(String msg, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
        System.out.println("QA接到消息"+msg); // 打印接收到的消息
        channel.basicAck(tag,true); // 确认消息已被消费
    }
}

此时来访问一下queueA

queueA的消费者也已经将消息消费掉了

RabbitMQ 死信交换机的详述➕应用_第9张图片

上面是queueA正常消费了,现在我直接将消息拒绝掉,并且不让它再重新入队了

package com.example.consumer.exchange;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component // 声明这是一个Spring组件
@RabbitListener(queues = "queueA") // 监听名为"queueA"的队列
public class DeadLetterReceiveA {

    @RabbitHandler // 当接收到消息时,调用此方法处理
    public void handler(String msg, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
        System.out.println("QA接到消息"+msg); // 打印接收到的消息
        channel.basicReject(tag,false); // 拒绝消息,不重新入队
        Thread.sleep(1000); // 等待1秒
    }
}

这时,再访问一下方法

刚开始这个消息会到queueA中,但是queueA会把它拒绝掉,拒绝之后消息就会到queueB 中

RabbitMQ 死信交换机的详述➕应用_第10张图片RabbitMQ 死信交换机的详述➕应用_第11张图片RabbitMQ 死信交换机的详述➕应用_第12张图片

此时消息全部都放在了queueB中,但是还没有消费,这是因为我们已经将消息确认的模式变成了手动,所以需要手动确认之后,消息才会被消费

package com.example.consumer.exchange;

import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Map;

@Component // 声明这是一个Spring组件
@RabbitListener(queues = {"queueB"}) // 监听名为"queueB"的队列
public class DeadLetterReceive {

    @RabbitHandler // 当接收到消息时,调用此方法处理
    public void handler(String msg, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception { // 参数为接收到的消息,类型为Map
        System.out.println("QB接到消息"+msg); // 打印接收到的消息
        channel.basicAck(tag,true); // 确认消息已被消费
    }
}

RabbitMQ 死信交换机的详述➕应用_第13张图片RabbitMQ 死信交换机的详述➕应用_第14张图片RabbitMQ 死信交换机的详述➕应用_第15张图片

好啦,今天的分享就到这了,希望能够帮到你呢! 

你可能感兴趣的:(RabbitMQ,rabbitmq,分布式)