目录
一、介绍
1、什么是死信队列(延迟队列)
2、应用场景
3、死信队列(延迟队列)的使用
4、死信消息来源
二、案例实践
1、案例一
2、案例二(消息接收确认 )
3、总结
- 死信,在官网中对应的单词为“Dead Letter”,它是 RabbitMQ 的一种消息机制。
- 死信队列(Dead Letter Queue)和延迟队列(Delay Queue)是两种不同的队列类型,但在实际应用中它们可以结合使用。
- 死信队列是当消息在队列中因为过期、被拒绝等原因无法正常处理时,会被重新发送到另一个交换机上,这个交换机就是死信交换机。死信队列可以用于实现重试机制、日志审计等特殊应用逻辑。
延迟队列则是一种特殊的队列类型,它允许将消息延迟指定的时间后才能被消费者消费。这种队列通常用于处理那些需要在特定时间点被处理的任务,例如定时任务、限时优惠等。在RabbitMQ中,可以通过设置消息的TTL(生存时间)来实现延迟队列的功能。当消息在队列中超过了TTL,它就会被移除并被发送到指定的死信交换机,进而被路由到死信队列中。
结合使用死信队列和延迟队列可以实现一些复杂的应用逻辑。
例如:
可以将某个需要延迟处理的消息发送到延迟队列中,并在消息过期之前将其存储在死信队列中。这样,当消息从延迟队列中移除时,它会被自动发送到死信队列中,然后由消费者消费并执行相应的操作。这种结合使用的方式可以提供更高的灵活性和可靠性,使得系统能够更好地应对各种异常情况。
死信队列(Dead Letter Queue)和延迟队列(Delay Queue)在以下应用场景中表现优异:
这些场景都是通过结合使用死信队列和延迟队列来提高系统的可靠性、鲁棒性和灵活性。通过合理地设置死信队列和延迟队列的参数,可以实现各种复杂的业务逻辑和异常处理机制。
使用死信队列(Dead Letter Queue)和延迟队列(Delay Queue)可以提高系统的可靠性和灵活性,特别是在处理异常情况、重试机制和定时任务等方面。
需要注意的是,在使用死信队列和延迟队列时,需要考虑系统的可用性和性能。如果大量的消息被放入死信队列中,可能会导致系统资源的过度消耗。因此,需要合理配置死信队列和延迟队列的大小和数量,以及监控和管理系统的性能和资源使用情况。
生产者
在生产者的Config里面添加队列 。
// =============死信队列===============
// 定义QueueA Bean,返回QueueA实例
@Bean
public Queue QueueA() {
Map config = new HashMap<>();
//message在该队列queue的存活时间最大为5秒
config.put("x-message-ttl", 5000);
//x-dead-letter-exchange参数是设置该队列的死信交换器(DLX)
config.put("x-dead-letter-exchange", "deadExchange");
//x-dead-letter-routing-key参数是给这个DLX指定路由键
config.put("x-dead-letter-routing-key", "deadQueue");
// 返回QueueA实例
return new Queue("QueueA", true, true, false, config);
}
// 定义DirectExchangeA Bean,返回DirectExchangeA实例
@Bean
public DirectExchange DirectExchangeA() {
// 返回DirectExchangeA实例
return new DirectExchange("DirectExchangeA");
}
// 定义bindingA Bean,将QueueA绑定到DirectExchangeA,并设置路由键为A.A
@Bean
public Binding bindingA() {
// 将QueueA绑定到DirectExchangeA
return BindingBuilder
// 设置路由键为A.A
.bind(QueueA())
.to(DirectExchangeA())
.with("A.A");
}
// 定义QueueB Bean,返回QueueB实例
@Bean
public Queue QueueB() {
// 返回QueueB实例
return new Queue("QueueB");
}
// 定义DirectExchangeB Bean,返回DirectExchangeB实例
@Bean
public DirectExchange DirectExchangeB() {
// 返回DirectExchangeB实例
return new DirectExchange("DirectExchangeB");
}
// 定义bindingB Bean,将QueueB绑定到DirectExchangeB,并设置路由键为B.B
@Bean
public Binding bindingB() {
// 将QueueB绑定到DirectExchangeB
return BindingBuilder
// 设置路由键为B.B
.bind(QueueB())
.to(DirectExchangeB())
.with("B.B");
}
消费者
在消费者里面添加QueueA和QueueB
QueueA
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@SuppressWarnings("all")
@Slf4j
@RabbitListener(queues = "QueueA")
public class ReceiverQA {
// 接收directExchange01交换机中Queue02队列消息的方法
@RabbitHandler
public void Queue02(String msg) {
log.warn("QueueA,接收到信息:" + msg);
}
}
QueueB
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@SuppressWarnings("all")
@Slf4j
@RabbitListener(queues = "QueueB")
public class ReceiverQB {
// 接收directExchange01交换机中Queue02队列消息的方法
@RabbitHandler
public void Queue02(String msg) {
log.warn("QueueB,接收到信息:" + msg);
}
}
编写Controller层
@RequestMapping("send5")
public String send5() {
rabbitTemplate.convertAndSend("DirectExchangeA", "A.A", "Hello,A");
return "";
}
结果:
如果某个服务忘记 ACK 了,则 RabbitMQ 不会再发送数据给它,因为 RabbitMQ 认为该服务的处理能力有限
ACK 机制还可以起到限流作用,比如在接收到某条消息时休眠几秒钟
消息确认模式有:
AcknowledgeMode.NONE:自动确认
AcknowledgeMode.AUTO:根据情况确认
AcknowledgeMode.MANUAL:手动确认
配置文件
默认情况下消息消费者是自动 ack (确认)消息的,如果要手动 ack(确认)则需要修改确认模式为 manualspring: rabbitmq: listener: simple: acknowledge-mode: manual
在消费者里的更改ReceiverQA
import com.rabbitmq.client.Channel; import lombok.extern.slf4j.Slf4j; 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 @SuppressWarnings("all") @Slf4j @RabbitListener(queues = "QueueA") public class ReceiverQA { // 接收directExchange01交换机中Queue02队列消息的方法 @RabbitHandler public void QueueA(String msg, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException { log.warn("QueueA,接收到信息:" + msg); channel.basicAck(tag, false); } }
需要注意的 basicAck 方法需要传递两个参数
deliveryTag(唯一标识 ID):当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel ,RabbitMQ 会用 basic.deliver 方法向消费者推送消息,这个方法携带了一个 delivery tag, 它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID,是一个单调递增的正整数,delivery tag 的范围仅限于 Channel
multiple:为了减少网络流量,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 delivery_tag 小于等于传入值的所有消息
持久化
exchange要持久化
queue要持久化
message要持久化
消息确认
启动消费返回(@ReturnList注解,生产者就可以知道哪些消息没有发出去)
生产者和Server(broker)之间的消息确认
消费者和Server(broker)之间的消息确认