🥳🥳Welcome 的Huihui's Code World ! !🥳🥳
接下来看看由辉辉所写的关于RabbitMQ的相关操作吧
目录
🥳🥳Welcome 的Huihui's Code World ! !🥳🥳
一.什么是死信交换机
这个在上篇博文已经讲到了,想看的可以点击一下
简单来说,就是生产者将消息投递到 queue 里了,consumer 从 queue 取出消息进行消费,如果它一直无法消费某条数据,那么可以把这条消息放入死信队列里面。等待 条件满足了再从死信队列中取出来再次消费,从而避免消息丢失。
⭐⭐注意:死信交换机本质上就是一个普通的交换机,只是因为队列设置了参数指定了死信交换机,这个普通的交换机才成为了死信的接收者
二. 死信队列的应用场景
- 错误处理:当消息无法被成功处理时,可以将其发送到死信队列,以便后续进行错误处理、日志记录或告警。
- 延迟消息:通过设置消息的过期时间,可以实现延迟消息的功能。当消息过期时,将被发送到死信队列,可以用于实现定时任务或延迟任务。
- 重试机制:当消息处理失败时,可以将消息发送到死信队列,并设置适当的重试策略。例如,可以使用指数退避算法对消息进行重试,以提高消息处理的成功率。
- 消息分析:通过监听死信队列,可以对无法被正常消费的消息进行分析和统计,以了解系统中存在的问题或异常情况
三.死信队列【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<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.将主队列绑定到主交换机
将主队列与主交换机进行绑定,以确保正常消息能够被正确路由到主队列。
// 将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<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:手动确认
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的消费者
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正常消费了,现在我直接将消息拒绝掉,并且不让它再重新入队了
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中,但是还没有消费,这是因为我们已经将消息确认的模式变成了手动,所以需要手动确认之后,消息才会被消费
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); // 确认消息已被消费 } }
好啦,今天的分享就到这了,希望能够帮到你呢!😊😊