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

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