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

🥳🥳Welcome 的Huihui's Code World ! !🥳🥳

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

目录

[🥳🥳Welcome 的Huihui's Code World ! !🥳🥳](#🥳🥳Welcome 的Huihui's Code World ! !🥳🥳)

一.什么是死信交换机

[二. 死信队列的应用场景](#二. 死信队列的应用场景)

三.死信队列【TTL】

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

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

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

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

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

6.消费者

7.测试

[四. 死信队列【Reject】](#四. 死信队列【Reject】)

1.配置消息确认模式

[⭐⭐消息消费者如何通知 Rabbit 消息消费成功?](#⭐⭐消息消费者如何通知 Rabbit 消息消费成功?)

2.消费者


一.什么是死信交换机

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

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

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

二. 死信队列的应用场景

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

三.死信队列【TTL】

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

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

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

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

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

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

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

java 复制代码
// 创建一个名为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参数为死信队列的路由键。

java 复制代码
 // 创建一个名为queueA的队列
    @Bean
    public Queue queueA() {
        Map<String, Object> 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);
    }

关于其中的参数:

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

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

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

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

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

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

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

6.消费者

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

java 复制代码
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<String, Object>
        System.out.println("QB接到消息"+msg); // 打印接收到的消息
    }
}

7.测试

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

现在可以看到queueA这里有六条消息

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

现在把queueB的消费者停掉

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

四. 死信队列【Reject】

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

1.配置消息确认模式

  • 消息确认模式有:

    • AcknowledgeMode.NONE:自动确认

    • AcknowledgeMode.AUTO:根据情况确认

    • AcknowledgeMode.MANUAL:手动确认

java 复制代码
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

消息接收确认

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

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

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

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

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

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

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

2.消费者

这个消费者是queueA的消费者

java 复制代码
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的消费者也已经将消息消费掉了

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

java 复制代码
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 中

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

java 复制代码
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<String, Object>
        System.out.println("QB接到消息"+msg); // 打印接收到的消息
        channel.basicAck(tag,true); // 确认消息已被消费
    }
}

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

相关推荐
初次攀爬者41 分钟前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
初次攀爬者2 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
让我上个超影吧3 天前
消息队列——RabbitMQ(高级)
java·rabbitmq
塔中妖3 天前
Windows 安装 RabbitMQ 详细教程(含 Erlang 环境配置)
windows·rabbitmq·erlang
断手当码农3 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者3 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端
业精于勤_荒于稀3 天前
物流订单系统99.99%可用性全链路容灾体系落地操作手册
分布式
Ronin3053 天前
信道管理模块和异步线程模块
开发语言·c++·rabbitmq·异步线程·信道管理
Asher05093 天前
Hadoop核心技术与实战指南
大数据·hadoop·分布式
凉凉的知识库3 天前
Go中的零值与空值,你搞懂了么?
分布式·面试·go