RabbitMQ高级特性--TTL和死信队列

目录

1.TTL

1.1设置消息的TTL

1.1.1配置交换机&队列

1.1.2发送消息

1.1.3运行程序观察结果

1.2设置队列的TTL

1.2.1配置队列和交换机的绑定关系

1.2.2发送消息

1.2.3运行程序观察结果

1.3两者区别

2.死信队列

[2.1 声名队列和交换机](#2.1 声名队列和交换机)

2.2正常队列绑定死信交换机

2.3制造死信产生的条件

[2.4 发送消息](#2.4 发送消息)

2.5测试死信

2.6常见面试题

2.6.1死信队列的概念

2.6.2死信的来源

2.6.3死信队列的应⽤场景


1.TTL

TTL(Time to Live, 过期时间), 即过期时间. RabbitMQ可以对消息和队列设置TTL.
当消息到达存活时间之后, 还没有被消费,就会被自动清除。

1.1设置消息的TTL

目前有两种方法可以设置消息的TTL

一是设置队列的TTL,队列中所有消息都有相同的过期时间,二是对消息本身进行单独设置,每条消息的TTL可以不同,如果两种方法一起使用,则消息的TTL以两者之间较小的那个数值决定。

先看针对每条消息设置TTL

针对每条消息设置TTL的方法是在发送消息的方法中加入expiration的属性参数,单位为ms

1.1.1配置交换机&队列

java 复制代码
//TTL
public static final String TTL_QUEUE = "ttl_queue";
public static final String TTL_EXCHANGE_NAME = "ttl_exchange";

//ttl
//1. 交换机
@Bean("ttlExchange")
public Exchange ttlExchange() {
 return
ExchangeBuilder.fanoutExchange(Constant.TTL_EXCHANGE_NAME).durable(true).build(
);
}
//2. 队列
@Bean("ttlQueue")
public Queue ttlQueue() {
 return QueueBuilder.durable(Constant.TTL_QUEUE).build();
}
//3. 队列和交换机绑定 Binding
@Bean("ttlBinding")
public Binding ttlBinding(@Qualifier("ttlExchange") FanoutExchange exchange, 
@Qualifier("ttlQueue") Queue queue) {
 return BindingBuilder.bind(queue).to(exchange);
}

1.1.2发送消息

java 复制代码
@RequestMapping("/ttl")
public String ttl(){
 String ttlTime = "10000";//10s
 rabbitTemplate.convertAndSend(Constant.TTL_EXCHANGE_NAME, "", "ttl 
test...", messagePostProcessor -> {
 messagePostProcessor.getMessageProperties().setExpiration(ttlTime);
 return messagePostProcessor;
 });
 return "发送成功!";
}

1.1.3运行程序观察结果

发送消息后可以看到Ready数为1

十秒后,刷新页面发现消息已消失

1.2设置队列的TTL

设置队列的TTL的方法是在创建队列时,加入x-message-ttl参数实现的,单位是ms

1.2.1配置队列和交换机的绑定关系

java 复制代码
public static final String TTL_QUEUE2 = "ttl_queue2";
//设置ttl
@Bean("ttlQueue2")
public Queue ttlQueue2() {
 //设置20秒过期
 return QueueBuilder.durable(Constant.TTL_QUEUE2).ttl(20*1000).build();
}
//3. 队列和交换机绑定 Binding
@Bean("ttlBinding2")
public Binding ttlBinding2(@Qualifier("ttlExchange") FanoutExchange exchange, 
@Qualifier("ttlQueue2") Queue queue) {
 return BindingBuilder.bind(queue).to(exchange);
}

设置过期时间,也可以采用以下方式:

java 复制代码
@Bean("ttlQueue2")
public Queue ttlQueue2() {
 Map<String, Object> arguments = new HashMap<>();
 arguments.put("x-message-ttl",20000);//20秒过期
 return
QueueBuilder.durable(Constant.TTL_QUEUE2).withArguments(arguments).build();
}

1.2.2发送消息

java 复制代码
@RequestMapping("/ttl")
 public String ttl() {
// String ttlTime = "30000";//10s
// //发送带ttl的消息
// rabbitTemplate.convertAndSend(Constant.TTL_EXCHANGE_NAME, "", "ttl test...", //messagePostProcessor -> {
// messagePostProcessor.getMessageProperties().setExpiration(ttlTime);
// return messagePostProcessor;
// });
 //发送不带ttl的消息
 rabbitTemplate.convertAndSend(Constant.TTL_EXCHANGE_NAME, "", "ttl 
test...");
 return "发送成功!";
 }

1.2.3运行程序观察结果

运行后发现,新增了一个队列,队列Features有一个TTL标识

发送消息后,可以看到Ready消息为1

采⽤发布订阅模式, 所有与该交换机绑定的队列都会收到消息

20s后,刷新页面,发现消息已经删除

由于ttl_queue队列, 未设置过期时间, 所以ttl_queue的消息未删除。

1.3两者区别

设置队列TTL属性的方法, ⼀旦消息过期, 就会从队列中删除
设置消息TTL的方法, 即使消息过期, 也不会马上从队列中删除, 而是在即将投递到消费者之前进⾏判定的
为什么这两种方法的处理方法不一致?
因为设置队列过期时间, 队列中已过期的消息肯定在队列头部, RabbitMQ只要定期从队头开始扫描是否有过期的消息即可.
而设置消息TTL的方式, 每条消息的过期时间不同, 如果要删除所有过期消息需要扫描整个队列, 所以不如等到此消息即将被消费时再判定是否过期, 如果过期再进行删除即可.

2.死信队列

死信(dead message)简单理解就是因为种种原因无法被消费的信息,就是死信

有死信,自然就有死信队列。当消息在一个队列中变成死信之后,它能被重新发送到另一个交换机中,这个交换机就是DLX(Dead Letter Exchange),绑定DLX的队列,就称为死信队列(Dead Letter Queue)简称DLQ

消息变成死信主要有以下几个原因:

1.消息被拒绝(Basic.Reject/Basic.Nack),并且设置requeue参数为false

2.消息过期

3.队列达到最大长度

2.1 声名队列和交换机

java 复制代码
//死信队列
public static final String DLX_EXCHANGE_NAME = "dlx_exchange";
public static final String DLX_QUEUE = "dlx_queue";
public static final String NORMAL_EXCHANGE_NAME = "normal_exchange";
public static final String NORMAL_QUEUE = "narmal_queue";
java 复制代码
/**
 * 死信队列相关配置
 */
@Configuration
public class DLXConfig {
 //死信交换机
 @Bean("dlxExchange")
 public Exchange dlxExchange(){
 return
ExchangeBuilder.topicExchange(Constant.DLX_EXCHANGE_NAME).durable(true).build()
;
 }
 //2. 死信队列
 @Bean("dlxQueue")
 public Queue dlxQueue() {
 return QueueBuilder.durable(Constant.DLX_QUEUE).build();
 }
 //3. 死信队列和交换机绑定 Binding
 @Bean("dlxBinding")
 public Binding dlxBinding(@Qualifier("dlxExchange") Exchange exchange, 
@Qualifier("dlxQueue") Queue queue) {
 return BindingBuilder.bind(queue).to(exchange).with("dlx").noargs();
 }
 //正常交换机
 @Bean("normalExchange")
 public Exchange normalExchange(){
 return
ExchangeBuilder.topicExchange(Constant.NORMAL_EXCHANGE_NAME).durable(true).buil
d();
 }
 //正常队列
 @Bean("normalQueue")
 public Queue normalQueue() {
 return QueueBuilder.durable(Constant.NORMAL_QUEUE).build();
 }
 //正常队列和交换机绑定 Binding
 @Bean("normalBinding")
 public Binding normalBinding(@Qualifier("normalExchange") Exchange 
exchange, @Qualifier("normalQueue") Queue queue) {
 return BindingBuilder.bind(queue).to(exchange).with("normal").noargs();
 }
}

2.2正常队列绑定死信交换机

当这个队列中存在死信时,RabbitMQ会自动的把这个消息重新发布到设置的DLX上,进而被路由到另一个队列,即死信队列,可以监听这个死信队列中的消息以进行相应的处理

java 复制代码
@Bean("normalQueue")
public Queue normalQueue() {
 Map<String, Object> arguments = new HashMap<>();
 arguments.put("x-dead-letter-exchange",Constant.DLX_EXCHANGE_NAME);//绑定死信队列
 arguments.put("x-dead-letter-routing-key","dlx");//设置发送给死信队列的
RoutingKey
 return QueueBuilder.durable(Constant.NORMAL_QUEUE).withArguments(arguments).build();
}

简写为

java 复制代码
return QueueBuilder.durable(Constant.NORMAL_QUEUE)
 .deadLetterExchange(Constant.DLX_EXCHANGE_NAME)
 .deadLetterRoutingKey("dlx").build();

2.3制造死信产生的条件

java 复制代码
@Bean("normalQueue")
public Queue normalQueue() {
 Map<String, Object> arguments = new HashMap<>();
 arguments.put("x-dead-letter-exchange",Constant.DLX_EXCHANGE_NAME);//绑定死
信队列
 arguments.put("x-dead-letter-routing-key","dlx");//设置发送给死信队列的
RoutingKey
 //制造死信产⽣的条件
 arguments.put("x-message-ttl",10000);//10秒过期
 arguments.put("x-max-length",10);//队列⻓度
 return
QueueBuilder.durable(Constant.NORMAL_QUEUE).withArguments(arguments).build();
}

简写为:

java 复制代码
return QueueBuilder.durable(Constant.NORMAL_QUEUE)
 .deadLetterExchange(Constant.DLX_EXCHANGE_NAME)
 .deadLetterRoutingKey("dlx")
 .ttl(10*1000)
 .maxLength(10L)
 .build();

2.4 发送消息

java 复制代码
 @RequestMapping("/dlx")
 public void dlx() {
 //测试过期时间, 当时间达到TTL, 消息⾃动进⼊到死信队列
 rabbitTemplate.convertAndSend(Constant.NORMAL_EXCHANGE_NAME, "normal", 
"dlx test...");
 //测试队列⻓度
// for (int i = 0; i < 20; i++) {
// rabbitTemplate.convertAndSend(Constant.NORMAL_EXCHANGE_NAME, "normal", "dlx test...");
// }
// //测试消息拒收
// rabbitTemplate.convertAndSend(Constant.NORMAL_EXCHANGE_NAME, "normal", "dlx test...");
 }

2.5测试死信

程序启动后观察队列:


队列Features说明:
D: durable的缩写, 设置持久化
TTL: Time to Live, 队列设置了TTL
Lim: 队列设置了⻓度(x-max-length)
DLX: 队列设置了死信交换机(x-dead-letter-exchange)
DLK: 队列设置了死信RoutingKey(x-dead-letter-routing-key)
测试过期时间,到达过期时间后,进入死信队列:
发送之后:

10s后:

⽣产者⾸先发送⼀条消息,然后经过交换器(normal_exchange)顺利地存储到队列(normal_queue)中.由于队列normal_queue设置了过期时间为10s, 在这10s内没有消费者消费这条消息, 那么判定这条消息过期. 由于设置了DLX, 过期之时, 消息会被丢给交换器(dlx_exchange)中, 这时根据RoutingKey匹配, 找到匹配的队列(dlx_queue), 最后消息被存储在queue.dlx这个死信队列里。

2.6常见面试题

死信队列作为RabbitMQ的高级特性,也是面试的一大重点

2.6.1死信队列的概念

死信(Dead Letter)是消息队列中的⼀种特殊消息, 它指的是那些⽆法被正常消费或处理的消息. 在消息队列系统中, 如RabbitMQ, 死信队列用于存储这些死信消息

2.6.2死信的来源

  1. 消息过期: 消息在队列中存活的时间超过了设定的TTL
  2. 消息被拒绝: 消费者在处理消息时, 可能因为消息内容错误, 处理逻辑异常等原因拒绝处理该消息. 如果拒绝时指定不重新⼊队(requeue=false), 消息也会成为死信.
  3. 队列满了: 当队列达到最大长度, 无法再容纳新的消息时, 新来的消息会被处理为死信.

2.6.3死信队列的应⽤场景

对于RabbitMQ来说, 死信队列是⼀个⾮常有⽤的特性. 它可以处理异常情况下,消息不能够被消费者正确消费而被置⼊死信队列中的情况, 应⽤程序可以通过消费这个死信队列中的内容来分析当时所遇到的
异常情况, 进而可以改善和优化系统.
⽐如: 用户支付订单之后, ⽀付系统会给订单系统返回当前订单的⽀付状态
为了保证⽀付信息不丢失, 需要使⽤到死信队列机制. 当消息消费异常时, 将消息投⼊到死信队列中, 由订单系统的其他消费者来监听这个队列, 并对数据进行处理(比如发送⼯单等,进行人工确认).
场景的应⽤场景还有:
消息重试:将死信消息重新发送到原队列或另⼀个队列进行重试处理.
消息丢弃:直接丢弃这些⽆法处理的消息,以避免它们占⽤系统资源.
⽇志收集:将死信消息作为⽇志收集起来,⽤于后续分析和问题定位

相关推荐
java1234_小锋6 小时前
Kafka中的消息是如何存储的?
分布式·kafka
老友@6 小时前
Kafka 深度解析:高性能设计、部署模式、容灾机制与 KRaft 新模式
分布式·kafka·kraft·高性能·容灾机制
余子桃6 小时前
Kafka的安装与使用(windows下python使用等)
分布式·kafka
java1234_小锋6 小时前
Kafka中的消息如何分配给不同的消费者?
分布式·kafka
小样vvv6 小时前
【Kafka】深入探讨 Kafka 如何保证一致性
分布式·kafka
陈平安Java and C8 小时前
RabbitMQ快速上手
rabbitmq
快来卷java11 小时前
深入剖析雪花算法:分布式ID生成的核心方案
java·数据库·redis·分布式·算法·缓存·dreamweaver
2401_8712905812 小时前
Hadoop 集群的常用命令
大数据·hadoop·分布式
冰 河12 小时前
《Mycat核心技术》第21章:高可用负载均衡集群的实现(HAProxy + Keepalived + Mycat)
分布式·微服务·程序员·分布式数据库·mycat
小样vvv14 小时前
【分布式】深入剖析 Sentinel 限流:原理、实现
分布式·c#·sentinel