延迟队列:https://www.cnblogs.com/mfrank/p/11260355.html
死信队列:https://www.cnblogs.com/mfrank/p/11184929.html
延迟队列+死信队列 大致流程图:
讨论什么是延迟队列之前我们得先知道什么是TTL:time to live 消息存活时间。
延迟队列:实际上不存在真正意义上的延迟队列(简单说就是在普通队列上加过期时间就是延迟队列)。但是我们可以通过设置消息的过期时间和死信队列来模拟出延时队列。消费者监听死信交换器绑定的队列,而不要监听消息发送的队列。
- 订单在十分钟之内未支付则自动取消。
- 新创建的店铺,如果在十天内都没有上传过商品,则自动发送消息提醒。
- 账单在一周内未支付,则自动结算。
- 用户注册成功后,如果三天内没有登陆则进行短信提醒。
- 用户发起退款,如果三天内没有得到处理则通知相关运营人员。
- 预定会议后,需要在预定的时间点前十分钟通知各个与会人员参加会议。
这些看起来似乎都可以使用定时服务解决,确实如果在数据量不大或者时间精确度不是那么注重的情况下是可以使用定时服务解决。不过在数据量大对失效时间要求准确的情况下建议使用RabbitMQ 延迟队列 + 死信队列来解决。
过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;过了之后消息将自动被删除。
消息在队列的生存时间一旦超过设置的TTL值,就称为Dead message
被投递到死信队列, 消费者将无法再收到该消息。
RabbitMQ 支持对 消息 和 队列 设置TTL。目前有两种方法可以设置:
如果上述两种同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。
第一种是在创建队列的时候设置队列的“x-message-ttl”属性,如下:
Map<String, Object> args = new HashMap<String, Object>();
args.put("x-message-ttl", 6000);
channel.queueDeclare(queueName, durable, exclusive, autoDelete, args);
第二种是对每条消息设置TTL,代码如下:
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
.expiration("6000")
.build();
channel.basicPublish(exchangeName, routingKey, mandatory, properties, "msg body".getBytes());
但这两种方式是有区别的,如果设置了队列的TTL属性,那么一旦消息过期,就会被队列丢弃,而第二种方式,消息即使过期,也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者之前判定的,如果当前队列有严重的消息积压情况,则已过期的消息也许还能存活较长时间。
另外,还需要注意的一点是,如果不设置TTL,表示消息永远不会过期,如果将TTL设置为0,则表示除非此时可以直接投递该消息到消费者,否则该消息将会被丢弃。
package com.example.ttl;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.util.HashMap;
import java.util.Map;
/**
* @author:
* @description: Producer 简单队列生产者
*/
public class Producer {
public static void main(String[] args) throws Exception {
// 1: 创建连接工厂,设置连接属性,2: 从连接工厂中获取,3: 从连接中打开通道channel
ConnectionFactory connectionFactory = new ConnectionFactory();
Connection connection = connectionFactory.newConnection("生产者");
Channel channel = connection.createChannel();
// 方式一:队列设置TTL
Map<String,Object> arguments = new HashMap<>();
arguments.put("x-message-ttl",5000);// 设置队列过期时间TTL
channel.queueDeclare("ttl-queue", false, false, false, arguments);
// 方式二:设置消息的过期时间。一般使用其一即可。两者同时使用时按照短的时间过期
AMQP.BasicProperties props = new AMQP.BasicProperties().builder()
.expiration("3000") // 消息过期时间
.build();
// param1:交换机名,param2: 队列名/路由key,param3:属性配置附加参数,param4:消息内容
channel.basicPublish("", "ttl-queue", props, "你好,消息队列!".getBytes());
System.out.println("消息发送成功!");
// 最后关闭通关和连接
channel.close();
connection.close();
}
}
然后可以从web页面查看该队列状况:1、可以发现队列有一个TTL的标签。2、三秒过后消息不管有没有被消费都会自动消失
SpringBoot 设置 队列 or 消息 的过期时间
创建队列、交换机、绑定关系
package com.example.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* 过期时间+延迟队列
**/
@Configuration
public class TTLRabbitMQConfig {
/********************************对队列设置过期时间*****************************************/
/**
* 1. 声明 Direct 模式交换机
*/
@Bean
public DirectExchange ttlDirectExchange() {
// param1: 交换机名、param2: 是否持久化、param3: 是否自动删除(无Queue与其绑定)
return new DirectExchange("ttl_direct_exchange", true, false);
}
/**
* 2. 队列过期时间
*/
@Bean
public Queue ttlDirectQueue() {
/*
* durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
* exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
* autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
* arguments:设置队列的属性参数
* x-message-ttl:设置过期时间,注意是int类型
*/
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 5000);
return new Queue("ttl.direct.queue", true, false, false, args);
}
/**
* 3. 绑定队列(不用指定routing key),参数名字要和bean名字一致
*/
@Bean
public Binding bindingTTLDirect() {
return BindingBuilder.bind(ttlDirectQueue()).to(ttlDirectExchange()).with("ttl");
}
/********************************对消息本身设置过期时间*****************************************/
/**
* 1. 用上面声明的交换机
* 2. 声明一个普通的队列
*/
@Bean
public Queue ttlMessageDirectQueue() {
return new Queue("ttl.message.direct.queue", true);
}
/**
* 3. 绑定队列(不用指定routing key),参数名字要和bean名字一致
*/
@Bean
public Binding bindingTTLMessageDirect() {
return BindingBuilder.bind(ttlMessageDirectQueue()).to(ttlDirectExchange()).with("ttl.message");
}
}
生产者发送消息
/**
* TTL过期时间测试——对队列设置过期时间
* 测试发送后查看web页面,5秒后消息自动消失了
*/
@Test
public void ttlQueueDirectSend() {
rabbitTemplate.convertAndSend("ttl_direct_exchange","ttl","TTL-队列过期时间");
}
/**
* TTL过期时间测试——对消息本身设置过期时间
* 测试发送后查看web页面,5秒后消息自动消失了
*/
@Test
public void ttlMessageDirectSend() {
MessagePostProcessor messagePostProcessor = message -> {
MessageProperties messageProperties = message.getMessageProperties();
messageProperties.setExpiration("5000");//字符串类型
messageProperties.setContentEncoding("UTF-8");
return message;
};
rabbitTemplate.convertAndSend("ttl_direct_exchange","ttl.message","TTL-消息过期时间", messagePostProcessor);
}
通过队列 TTL 缺点:每增加一个时间需求,就需要增一个队列。这样不太适用动态设置过期时间。
那么我们可以通过对消息本身设置过期时间,可是这也存在一个缺点(提前给结论):消息的过期是"同步"的。
解释:第一条消息过期时间是20s,第二条是2s。第二个消息会在等第一个消息成为死信后才会“死亡“。因为这是在同一个队列,必须前一个消费,第二个才能消费,所以就出现了时序问题。
实战代码
1、定义队列和交换机,并且绑定
package com.example.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 过期时间+延迟队列
**/
@Configuration
public class TTLMessagesRabbitMQConfig {
/********************************对消息本身设置过期时间*****************************************/
// 1. 声明 fanout 模式交换机
@Bean
public FanoutExchange ttlMessagesExchange(){
return ExchangeBuilder.fanoutExchange("ttl-message.fanout-exchange").build();
}
// 2. 声明一个普通的队列,并设置消息
@Bean
public Queue ttlMessagesQueue() {
return QueueBuilder.durable("ttl-messages.queue")
.withArgument("x-dead-letter-exchange", "dead-ttl-message.fanout-exchange")
.build();
}
// 3. 绑定队列与交换机关系
@Bean
public Binding bindingTTLMessagesFanout() {
return BindingBuilder.bind(ttlMessagesQueue()).to(ttlMessagesExchange());
}
/********************************声明死信交换机和队列*****************************************/
// 1. 声明死信交换机-Fanout模式
@Bean
public FanoutExchange deadFanoutExchange(){
return ExchangeBuilder.fanoutExchange("dead-ttl-message.fanout-exchange").build();
}
// 2. 声明一个死信队列
@Bean
public Queue deadTTLMessagesQueue() {
return QueueBuilder.durable("dead-ttl-messages.queue").build();
}
// 3. 绑定死信队列与死信交换机关系
@Bean
public Binding bindingDeadTTLMessagesFanout() {
return BindingBuilder.bind(deadTTLMessagesQueue()).to(deadFanoutExchange());
}
}
2、消费者监听消费:
package com.example.service;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class DeadTTLMessagesConsumer {
// 监听死信队列
@RabbitListener(queues = "dead-ttl-messages.queue")
public void processOne(String msg) {
System.out.println("死信队列收到消息:" + msg);
}
}
3、发送者发送消息:
/**
* 测试对消息设置过期时间。
* 先发送一条20s过期的消息,然后再发送一条2s过期的消息
* 可以发现,第一条消息过期时间是20s,第二条是2s。第二个消息会在等第一个消息成为死信后才会“死亡“。
* 结论:消息的过期是"同步"的。
*/
@Test
public void ttlMessagesTest(){
rabbitTemplate.convertAndSend("ttl-message.fanout-exchange","","20s过期的消息", message -> {
message.getMessageProperties().setExpiration("20000");
return message;
});
rabbitTemplate.convertAndSend("ttl-message.fanout-exchange","","2s过期的消息", message -> {
message.getMessageProperties().setExpiration("2000");
return message;
});
}
4、消费端控制台输出:
死信队列收到消息:20s过期的消息
死信队列收到消息:2s过期的消息
可以发现TTL时间短的比TTL时间长的后“死亡”
Delayed Message 插件 及实现原理
上文的问题确实是一个硬伤,如果不能实现在消息粒度上添加TTL,并使其在设置的TTL时间及时死亡,就无法设计成一个通用的延时队列。针对消息无序的不妨看下以下解决方案。
上面使用 DLX + TTL 的模式,消息首先会路由到一个正常的队列,根据设置的 TTL 进入死信队列,与之不同的是通过 x-delayed-message 声明的交换机,它的消息在发布之后不会立即进入队列,先将消息保存至 Mnesia(一个分布式数据库管理系统,适合于电信和其它需要持续运行和具备软实时特性的 Erlang 应用。目前资料介绍的不是很多)
这个插件将会尝试确认消息是否过期,首先要确保消息的延迟范围是 Delay > 0, Delay =< ?ERL_MAX_T(在 Erlang 中可以被设置的范围为 (2^32)-1 毫秒),如果消息过期通过 x-delayed-type 类型标记的交换机投递至目标队列,整个消息的投递过程也就完成了。
插件安装
RabbitMQ的延迟插件下载并安装:https://www.rabbitmq.com/community-plugins.html
下载rabbitmq_delayed_message_exchange
插件(对应RabbitMQ版本),解压到RabbitMQ的插件(plugins)目录
进入RabbitMQ安装目录的sbin目录下,使用如下命令启用延迟插件,然后重启RabbitMQ
# 启动delayed-message插件:
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
# 重启RabbitMQ
rabbitmqctl stop_app && rabbitmqctl start_app
# Liunx环境下下载
# wget https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/download/3.8.9/rabbitmq_delayed_message_exchange-3.8.9-0199d11c.ez
出现如下信息代表成功:
C:\Users\lenovo>rabbitmq-plugins enable rabbitmq_delayed_message_exchange
Enabling plugins on node rabbit@LAPTOP-671C76TJ:
rabbitmq_delayed_message_exchange
The following plugins have been configured:
rabbitmq_delayed_message_exchange
rabbitmq_management
rabbitmq_management_agent
rabbitmq_web_dispatch
Applying plugin configuration to rabbit@LAPTOP-671C76TJ...
The following plugins have been enabled:
rabbitmq_delayed_message_exchange
started 1 plugins.
代码实战
1、定义交换机和队列,并绑定关系
package com.example.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* 延迟队列插件
**/
@Configuration
public class TTLDelayedPluginRabbitMQConfig {
// 1. 延迟插件消息队列所绑定的交换机
@Bean
public CustomExchange customExchange(){
// 创建一个自定义交换机,可以发送延迟消息
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
// 参数二为类型:必须是x-delayed-message
return new CustomExchange("ttl-delayed.exchange", "x-delayed-message", true, false, args);
}
// 2. 延迟插件队列(一个普通的队列)
@Bean
public Queue ttlDelayedQueue() {
return QueueBuilder.durable("ttl-delayed.queue").build();
}
// 3. 绑定队列与交换机关系
@Bean
public Binding bindingTTLDelayed() {
return BindingBuilder.bind(ttlDelayedQueue()).to(customExchange()).with("ttl-delayed.key").noargs();
}
}
2、消费者监听消息
package com.example.service;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class TTLDelayedPluginConsumer {
// 监听死信队列
@RabbitListener(queues = "ttl-delayed.queue")
public void processOne(String msg) {
System.out.println("死信队列收到消息:" + msg);
}
}
3、发送者发送消息
/**
* Delayed Message 插件:测试对消息设置过期时间
*/
@Test
public void ttlDelayedPluginTest(){
rabbitTemplate.convertAndSend("ttl-delayed.exchange",
"ttl-delayed.key","20s过期的消息", message -> {
message.getMessageProperties().setDelay(20000);
return message;
});
rabbitTemplate.convertAndSend("ttl-delayed.exchange",
"ttl-delayed.key","2s过期的消息", message -> {
message.getMessageProperties().setDelay(2000);
return message;
});
}
4、消费端控制台输出:
死信队列收到消息:2s过期的消息
死信队列收到消息:20s过期的消息
1、什么是死信队列(Dead Letter):没有及时消费的消息存放的队列。
2、什么是死信交换机:DLX(Dead-Letter-Exchange)当消息在队列中变成死信之后,它能被重新发送到另一个交换机中,这个交换机就是DLX ,绑定DLX的队列就称之为死信队列(死信交换机与队列一样都是一个正常的交换机和队列,和一般的交换机和队列没有区别)
3、什么是死信消息(Dead-Message):未被正常处理的消息。出现死信的情况:
死信消息会被RabbitMQ进行特殊处理,如果配置了死信队列,那么该消息将会被丢进死信队列中,如果没配置,则丢弃该消息。
消息大致流转过程:
配置死信队列大概可以分为以下步骤:
JAVA 代码实战:
package com.example.dlx;
import com.rabbitmq.client.*;
import java.util.HashMap;
import java.util.Map;
/**
* @author:
* @description: Producer 简单队列生产者
*/
public class Producer {
public static void main(String[] args) throws Exception {
// 1: 创建连接工厂,设置连接属性,2: 从连接工厂中获取,3: 从连接中打开通道channel
ConnectionFactory connectionFactory = new ConnectionFactory();
Connection connection = connectionFactory.newConnection("生产者");
Channel channel = connection.createChannel();
// 创建死信交换机和队列
channel.exchangeDeclare("dlx-exchange", BuiltinExchangeType.DIRECT);
channel.queueDeclare("dlx", false, false, false, null);
channel.queueBind("dlx", "dlx-exchange", "dlx.key");
/**
* @params1:queue 队列的名称
* @params2:durable 队列是否持久化(即存盘)
* @params3:exclusive 是否排他,即是否私有的
* @params4:autoDelete 是否自动删除,当此队列的连接数为0时,此队列会销毁(无论队列中是否还有数据)
* @params5:arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等
* x-message-ttl:设置队列过期时间TTL
* x-max-length:设置队列最大存储消息数,超出的要么丢弃要么放进死信队列
* x-dead-letter-exchange:死信交换机名称
* */
Map<String,Object> arguments = new HashMap<>();
arguments.put("x-message-ttl",5000);
arguments.put("x-max-length", 5);
arguments.put("x-dead-letter-exchange", "dlx-exchange");
arguments.put("x-dead-letter-routing-key", "dlx.key");
channel.queueDeclare("ttl-queue", false, false, false, arguments);
// param1:交换机名,param2: 队列名/路由key,param3:属性配置附加参数,param4:消息内容
channel.basicPublish("", "ttl-queue", null, "你好,消息队列!".getBytes());
System.out.println("消息发送成功!");
// 最后关闭通关和连接
channel.close();
connection.close();
}
}
声明死信和延迟交换机 + 队列并且绑定关系
package com.example.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 延迟队列 + 死信队列
**/
@Configuration
public class DeadRabbitMQConfig {
// 死信交换机、队列名、路由键
public static final String DEAD_LETTER_EXCHANGE = "dead.letter.direct-exchange";
public static final String DEAD_LETTER_QUEUE_NAME = "dead.letter.dead-queue";
public static final String DEAD_LETTER_QUEUE_ROUTING_KEY = "dead.letter.routing-key";
// 业务交换机、队列名、路由键
public static final String BUSINESS_EXCHANGE_NAME = "dead.letter.business-exchange";
public static final String BUSINESS_QUEUE_NAME = "dead.letter.business-queue";
public static final String BUSINESS_QUEUE_ROUTING_KEY = "dead.letter.business-queue.routing-key";
// 1. 声明死信交换机-Direct模式
@Bean
public DirectExchange deadDirectExchange() {
return ExchangeBuilder.directExchange(DEAD_LETTER_EXCHANGE).build();
}
// 2. 声明死信队列
@Bean
public Queue deadDirectQueue() {
return QueueBuilder.durable(DEAD_LETTER_QUEUE_NAME).build();
}
// 3. 声明死信队列与死信交换机绑定关系
@Bean
public Binding bindingDeadDirect() {
return BindingBuilder.bind(deadDirectQueue()).to(deadDirectExchange()).with(DEAD_LETTER_QUEUE_ROUTING_KEY);
}
// 1. 声明业务Exchange-Direct模式—也可以称为 延迟交换机
@Bean
public DirectExchange ttlDeadDirectExchange() {
return ExchangeBuilder.directExchange(BUSINESS_EXCHANGE_NAME).build();
}
// 2. 声明业务队列—也可以称为 延迟队列
@Bean
public Queue ttlDeadDirectQueue() {
return QueueBuilder.durable(BUSINESS_QUEUE_NAME)
// 设置过期时间,注意是int类型
.withArgument("x-message-ttl", 5000)
// 设置队列最大存储消息数,超出的要么丢弃要么放进死信队列
.withArgument("x-max-length", 3)
// 指定死信交换机,ttl过期以后消息自动转发到指定交换机
.withArgument("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE)
// 死信消息路由键
.withArgument("x-dead-letter-routing-key", DEAD_LETTER_QUEUE_ROUTING_KEY)
.build();
}
// 3 声明延迟队列与延迟交换机绑定关系
@Bean
public Binding bindingTTLDeadDirect() {
return BindingBuilder.bind(ttlDeadDirectQueue()).to(ttlDeadDirectExchange()).with(BUSINESS_QUEUE_ROUTING_KEY);
}
}
消息发送端测试代码:
/**
* 延迟队列 + 死信队列测试
* 发现消息到业务队列中,然后没有指定消费者消费或者消费是抛异常拒绝消息,
* 就会流转到死信交换机中,然后到指定的路由中
*/
@Test
public void deadTest(){
for (int i = 0; i < 5; i++) {
String businessExchangeName = DeadRabbitMQConfig.BUSINESS_EXCHANGE_NAME;
String businessQueueRoutingKey = DeadRabbitMQConfig.BUSINESS_QUEUE_ROUTING_KEY;
rabbitTemplate.convertAndSend(businessExchangeName, businessQueueRoutingKey,"死信队列测试" + i);
}
}
查看web页面可以发现当消息超过5秒没有被消费或者消息超过3条以上时,都会被转移到死信交换机然后到指定的死信队列中。我们只需要监听指定的死信队列就可以做其他业务操作了
消费者端代码监听:
package com.example.service;
import com.example.config.DeadRabbitMQConfig;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class DeadConsumer {
// 监听死信队列
@RabbitListener(queues = DeadRabbitMQConfig.DEAD_LETTER_QUEUE_NAME)
public void processOne(String msg) {
System.out.println("死信队列收到消息:" + msg);
}
}
消费端监听控制台输出:
死信队列收到消息:死信队列测试0
死信队列收到消息:死信队列测试1
死信队列收到消息:死信队列测试2
死信队列收到消息:死信队列测试3
死信队列收到消息:死信队列测试4
最终所有的死信消息都会转入到死信队列中。
如果队列配置了参数x-dead-letter-routing-key
,死信路由key将会被替换。如果不设置,则使用消息的原来的路由键值。
举个栗子:
如果原有消息的路由key是testA
,被发送到业务Exchage中,然后被投递到业务队列QueueA中,如果该队列没有配置参数x-dead-letter-routing-key
,则该消息成为死信后,将保留原有的路由keytestA
,如果配置了该参数,并且值设置为testB
,那么该消息成为死信后,路由key将会被替换为testB
,然后被抛到死信交换机中。另外,由于被抛到了死信交换机,所以消息的ExchangeName也会被替换为死信交换机的名称。
消息的Header中,也会添加很多奇奇怪怪的字段,修改一下上面的代码,在死信队列的消费者中添加一行日志输出:
log.info("死信消息properties:{}", message.getMessageProperties());
然后重新运行一次,即可得到死信消息Header中被添加的信息:
死信消息properties:MessageProperties [headers={x-first-death-exchange=dead.letter.business-exchange, x-death=[{reason=expired, count=1, exchange=dead.letter..business-exchange, time=Wed Jun 02 15:57:55 CST 2021, routing-keys=[dead.letter.business-queue.routing-key], queue=dead.letter.business-queue}], x-first-death-reason=expired, x-first-death-queue=dead.letter.business-queue}, contentType=text/plain, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, redelivered=false, receivedExchange=dead.letter.direct-exchange, receivedRoutingKey=dead.letter.routing-key, deliveryTag=3, consumerTag=amq.ctag-CX8-JD0orH03gXXlloL-AQ, consumerQueue=dead.letter.dead-queue]
Header中看起来有很多信息,实际上并不多,只是值比较长而已。下面就简单说明一下Header中的值:
字段名 | 含义 |
---|---|
x-first-death-exchange | 第一次被抛入的死信交换机的名称 |
x-first-death-reason | 第一次成为死信的原因,rejected :消息在重新进入队列时被队列拒绝,由于default-requeue-rejected 参数被设置为false 。expired :消息过期。maxlen : 队列内消息数量超过队列最大容量 |
x-first-death-queue | 第一次成为死信前所在队列名称 |
x-death | 历次被投入死信交换机的信息列表,同一个消息每次进入一个死信交换机,这个数组的信息就会被更新 |
总结一下死信消息的生命周期:
死信消息是RabbitMQ为我们做的一层保证,其实我们也可以不使用死信队列,而是在消息消费异常时,将消息主动投递到另一个交换机中,当你明白了这些之后,这些Exchange和Queue想怎样配合就能怎么配合。比如从死信队列拉取消息,然后发送邮件、短信、钉钉通知来通知开发人员关注。或者将消息重新投递到一个队列然后设置过期时间,来进行延时消费。