死信和死信队列的概念

什么是死信?简单来说就是无法被消费和处理的消息。一般生产者将消息投递到broker或者queue,消费者直接从中取出消息进行消费。但有时因为某些原因导致消息不能被消费,导致消息积压在队列中,这样的消息如果没有后续的处理就会变成死信,那么专门存放死信的队列就是死信队列。

什么是死信交换机?

那么什么是死信交换机呢?死信交换机是指专门将死信路由到死信队列的交换机。

产生死信的原因

根据官方文档,我们发现一般有三种场景会产生死信。

消息超过TTL,即消息过期

2.消息被nack或reject,且不予重新入队

3.队列达到最大长度

死信队列实战和应用

死信队列的应用并不难,无非就是多定义了一个交换机、routingKey和队列罢了。在声明普通队列时传入Map参数,往Map中put死信队列名称、死信routingKey、消息TTL等参数即可完成死信自动投递到死信队列的流程。通过如下代码即可绑定普通队列和死信交换机了,而且还能设置routingKey和队列长度等参数,无需像传统的那样通过channel绑定。

Map<String, Object> arguments = new HashMap<>();
// 过期时间
arguments.put("x-message-ttl", 10000);
// 正常队列设置死信交换机
arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
// 设置死信
routingKey arguments.put("x-dead-letter-routing-key", "lisi");
// 设置正常队列的长度限制 arguments.put("x-max-length", 10);

流程图:

生产者Producer:

public class Producer {
// 普通交换机名称
public static final String NORMAL_EXCHANGE = "normal_exchange";
public static void main(String[] args) throws IOException {
Channel channel = RabbitMQUtils.getChannel();
//死信消息 设置TTL时间
AMQP.BasicProperties properties = new AMQP.BasicProperties()
.builder().expiration("10000").build(); // 延迟消息
for (int i = 0;i < 10;i++) {
String message = i + "info";
channel.basicPublish(NORMAL_EXCHANGE, "zhangsan", properties,
message.getBytes());
}
}
}

普通队列消费者C1:

public class Consumer01 {
// 普通交换机名称
public static final String NORMAL_EXCHANGE = "normal_exchange";
// 死信交换机名称
public static final String DEAD_EXCHANGE = "dead_exchange";
// 普通队列名称
public static final String NORMAL_QUEUE = "normal_queue";
// 死信队列名称
public static final String DEAD_QUEUE = "dead_queue"; public static void main(String[] args) throws IOException {
Channel channel = RabbitMQUtils.getChannel();
// 声明死信和普通交换机,类型为direct
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);
// 声明普通队列
Map<String, Object> arguments = new HashMap<>();
// 过期时间
arguments.put("x-message-ttl", 10000);
// 正常队列设置死信交换机
arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
// 设置死信routingKey
arguments.put("x-dead-letter-routing-key", "lisi");
// 设置正常队列的长度限制
arguments.put("x-max-length", 10); // 声明普通队列
channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);
// 声明死信队列
channel.queueDeclare(DEAD_QUEUE, false, false, false, null); channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "zhangsan");
channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "lisi");
System.out.println("consumer01等待接收消息"); DeliverCallback deliverCallback = (consumerTag, message) -> {
String msg = new String(message.getBody(), "UTF-8");
if (msg.equals("info5")) {
System.out.println("consumer01接收的消息:" + new String(message.getBody()));
System.out.println(msg + ":此消息是被拒绝的");
channel.basicReject(message.getEnvelope().getDeliveryTag(), false); //拒绝此消息并不放回普通队列
} else {
System.out.println("consumer01接收的消息:" + new String(message.getBody()));
channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
}
}; CancelCallback cancelCallback = consumerTag -> {
System.out.println("C1取消消息");
};
channel.basicConsume(NORMAL_QUEUE, false, deliverCallback, cancelCallback);
}
}

死信队列消费者C2

public class Consumer02 {
// 死信队列名称
public static final String DEAD_QUEUE = "dead_queue"; public static void main(String[] args) throws IOException {
Channel channel = RabbitMQUtils.getChannel();
System.out.println("consumer02等待接收消息");
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("consumer02接收的消息:" + new String(message.getBody()));
}; CancelCallback cancelCallback = consumerTag -> {
System.out.println("C2取消消息");
};
channel.basicConsume(DEAD_QUEUE, true, deliverCallback, cancelCallback);
}
}

依次启动生产者,和两个消费者,并停掉普通队列的消费者,我们发现生产者发送的消息被死信队列消费者C2给接收了。

在上面的代码中,我在普通队列中设置了消息的TTL为5s,但是我又在生产者设置发送的消息TTL为10s,那么RabbitMQ会以哪个为准呢?其实RabbitMQ会以较短的TTL为准

BI项目添加死信队列

声明交换机、队列和routingKey的配置类

@Configuration
public class TtlQueueConfig {
private final String COMMON_EXCHANGE = "bi_common_exchange"; // 普通交换机名称
private final String COMMON_QUEUE = "bi_common_queue"; // 普通队列名称
private final String DEAD_LETTER_EXCHANGE = "bi_dead_letter_exchange"; // 死信交换机名称
private final String DEAD_LETTER_QUEUE = "bi_dead_letter_queue"; // 死信队列名称
private final String COMMON_ROUTINGKEY = "bi_common_routingKey"; // 普通routingKey
private final String DEAD_LETTER_ROUTINGKEY = "bi_dead_letter_routingKey"; // 死信routingKey // 普通交换机
@Bean("commonExchange")
public DirectExchange commonExchange() {
return new DirectExchange(COMMON_EXCHANGE);
} // 死信交换机
@Bean("deadLetterExchange")
public DirectExchange deadLetterExchange() {
return new DirectExchange(DEAD_LETTER_EXCHANGE);
} // 普通队列
@Bean("commonQueue")
public Queue commonQueue() {
Map<String, Object> map = new HashMap<>(3);
map.put("x-message-ttl", 20000);
map.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE);
map.put("x-dead-letter-routing-key", DEAD_LETTER_ROUTINGKEY);
return QueueBuilder.durable(COMMON_QUEUE).withArguments(map).build();
} // 死信队列
@Bean("deadLetterQueue")
public Queue deadLetterQueue() {
return QueueBuilder.durable(DEAD_LETTER_QUEUE).build();
} @Bean
public Binding commonQueueBindingCommonExchange(@Qualifier("commonQueue") Queue commonQueue,
@Qualifier("commonExchange") DirectExchange commonExchange) {
return BindingBuilder.bind(commonQueue).to(commonExchange).with(COMMON_ROUTINGKEY);
} @Bean
public Binding deadQueueBindingDeadExchange(@Qualifier("deadLetterQueue") Queue deadLetterQueue,
@Qualifier("deadLetterExchange") DirectExchange deadLetterExchange){
return BindingBuilder.bind(deadLetterQueue).to(deadLetterExchange).with(DEAD_LETTER_ROUTINGKEY);
}
}

普通消费者(负责异步生成图表信息)

@Configuration
// 如果失败,消息拒绝
channel.basicNack(deliveryTag, false, false);
log.info("消息为空拒绝接收");
log.info("此消息正在被转发到死信队列中");
} long chartId = Long.parseLong(message);
Chart chart = chartService.getById(chartId);
if (chart == null) {
channel.basicNack(deliveryTag, false, false);
log.info("图标为空拒绝接收");
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "图表为空");
} // 先修改图表任务状态为“执行中”。等执行成功后,修改为“已完成”、保存执行结果;执行失败后,状态修改为“失败”,记录任务失败信息。
Chart updateChart = new Chart();
updateChart.setId(chart.getId());
updateChart.setStatus("running");
boolean b = chartService.updateById(updateChart);
if (!b) {
channel.basicNack(deliveryTag, false, false);
handlerChartUpdateError(chart.getId(), "更新图表执行状态失败");
return;
}
// 调用AI
String result = aiManager.doChat(CommonConstant.BI_MODEL_ID, buildUserInput(chart));
String[] splits = result.split("【【【【【");
if (splits.length < 3) {
channel.basicNack(deliveryTag, false, false);
handlerChartUpdateError(chart.getId(), "AI生成错误");
return;
}
String genChart = splits[1].trim();
String genResult = splits[2].trim();
Chart updateChartResult = new Chart();
updateChartResult.setId(chart.getId());
updateChartResult.setGenChart(genChart);
updateChartResult.setGenResult(genResult);
updateChartResult.setStatus("succeed");
boolean updateResult = chartService.updateById(updateChartResult);
if (!updateResult) {
channel.basicNack(deliveryTag, false, false);
handlerChartUpdateError(chart.getId(), "更新图表成功状态失败");
}
Long userId = chartService.queryUserIdByChartId(chartId);
String myChartId = String.format("lingxibi:chart:list:%s", userId);
redisTemplate.delete(myChartId); // 如果任务执行成功,手动执行ack
channel.basicAck(deliveryTag, false);
} private void handlerChartUpdateError(long chartId, String execMessage) {
Chart updateChartResult = new Chart();
updateChartResult.setId(chartId);
updateChartResult.setStatus("failed");
updateChartResult.setExecMessage(execMessage);
boolean updateResult = chartService.updateById(updateChartResult);
if (!updateResult) {
log.error("更新图表失败状态失败" + chartId + "," + execMessage);
}
} /**
* 构建用户输入
* @param chart
* @return
*/
private String buildUserInput(Chart chart) {
String goal = chart.getGoal();
String chartType = chart.getChartType();
String csvData = chart.getChartData(); // 构造用户输入
StringBuilder userInput = new StringBuilder();
userInput.append("分析需求:").append("\n");
// 拼接分析目标
String userGoal = goal;
if (StringUtils.isNotBlank(chartType)) {
userGoal += ",请使用" + chartType;
}
userInput.append(userGoal).append("\n");
userInput.append("原始数据:").append("\n");
// 压缩后的数据 userInput.append(csvData).append("\n");
return userInput.toString();
}
}

死信队列消费者(负责处理死信)

 收到死信后我是直接确认了,这种方式可能不好,你也可以换成其他方式比如重新入队,或者写入数据库并打上日志等等。
@Component
@Slf4j
public class TtlQueueConsumer {
@Resource
BIMessageProducer biMessageProducer; @SneakyThrows
@RabbitListener(queues = "bi_dead_letter_queue", ackMode = "MANUAL")
public void doTTLMessage(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
log.info("已经接受到死信消息:{}", message);
biMessageProducer.sendMessage(message);
channel.basicAck(deliveryTag, false);
}
}

如果我的文章对你有帮助的话,不妨给我点个赞呗,我会持续带来不一样的内容。如果对Java相关知识感兴趣的话,可以关注我,带你走进Java的世界。

RabbitMQ——死信队列介绍和应用的更多相关文章

  1. RabbitMQ死信队列另类用法之复合死信

    前言 在业务开发过程中,我们常常需要做一些定时任务,这些任务一般用来做监控或者清理任务,比如在订单的业务场景中,用户在创建订单后一段时间内,没有完成支付,系统将自动取消该订单,并将库存返回到商品中,又 ...

  2. rabbitmq死信队列消息监听

    #邮件通知并发送队列消息#!/bin/bash maillog="/var/log/mq.maillog" message_file="/tmp/mq_message&q ...

  3. RabbitMQ 死信队列 延时

    package com.hs.services.config; import java.util.HashMap; import java.util.Map; import org.springfra ...

  4. RabbitMQ死信队列

    关于RabbitMQ死信队列 死信队列 听上去像 消息“死”了     其实也有点这个意思,死信队列  是 当消息在一个队列 因为下列原因: 消息被拒绝(basic.reject/ basic.nac ...

  5. 【RabbitMQ】一文带你搞定RabbitMQ死信队列

    本文口味:爆炒鱿鱼   预计阅读:15分钟 一.说明 RabbitMQ是流行的开源消息队列系统,使用erlang语言开发,由于其社区活跃度高,维护更新较快,性能稳定,深得很多企业的欢心(当然,也包括我 ...

  6. springboot rabbitmq 死信队列应用场景和完整demo

    何为死信队列? 死信队列实际上就是,当我们的业务队列处理失败(比如抛异常并且达到了retry的上限),就会将消息重新投递到另一个Exchange(Dead Letter Exchanges),该Exc ...

  7. 【MQ中间件】RabbitMQ -- RabbitMQ死信队列及内存监控(4)

    1.RabbitMQ TTL及死信队列 1.1.TTL概述 过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取:过了之后消息将自动被删除.RabbitMQ可以对消息和队列设 ...

  8. rabbitmq死信队列和延时队列的使用

    死信队列&死信交换器:DLX 全称(Dead-Letter-Exchange),称之为死信交换器,当消息变成一个死信之后,如果这个消息所在的队列存在x-dead-letter-exchange ...

  9. .Net Core&RabbitMQ死信队列

    过期时间 RabbitMQ可以为消息和队列设置过期时间Time To Live(TTL).其目的即过期. 消息过期时间 消息存储在队列中时,如果想为其设置一个有限的生命周期,而不是一直存储着,可以为其 ...

  10. RabbitMq死信队列(接盘侠)

    队列创建之后,后期对其修改或者参数添加会报错.需要把队列重新删除,重新创建线上环境不能把队列删除,优雅安全的方式是重新建一个队列,把死信队列相关的队列进行绑定 在有过期时间的队列中设定最大接收能力5条 ...

随机推荐

  1. [oeasy]python049_帮助手册_pydoc_manual_document

    帮助手册 回忆上次内容 上次了解了注释 注释是为了让程序更可读 注释不会影响程序运行速度   注释分为两种 单行的 以#开头 不能是字符串当中的#   多行的 三个" 三个'     多行注 ...

  2. [oeasy]python0132_[趣味拓展]emoji_表情符号_抽象话_由来_流汗黄豆

    emoji表情符号 回忆上次内容 上次了解了unicode 和 utf-8 unicode是字符集 utf-8是一种可变长度的编码方式 utf-8是实现unicode的存储和传输的现实的方式   ​ ...

  3. AT_abc215_d 题解

    洛谷链接&Atcoder 链接 本篇题解为此题较简单做法及较少码量,并且码风优良,请放心阅读. 题目简述 给定 \(N\),\(M\) 及含有 \(N\) 个整数的序列 \(A\). 求 \( ...

  4. 在Python中使用SWCNN去除水印

    在Python中使用SWCNN去除水印 说明 首次发表日期:2024-07-17 SWCNN Github官方仓库: https://github.com/hellloxiaotian/SWCNN S ...

  5. JavaScript小技巧~this的保存

    在JavaScript中如果一个函数返回一个函数,此时this容易丢失,为了不使this丢失,需要在函数内部保存this. change() { let _this=this return funct ...

  6. 计算复杂性学习笔记(TCS笔记)

    感觉最近太摆了,每天很空虚,不愿意搞应试那一套,还是得学点东西.还是准备一边学tcs一边搞搞oi好打icpc,搞这个纯属兴趣,也是填个坑.最近进了一个初中生建的数学交流群,感觉群里初中生也很厉害,开始 ...

  7. Maven创建Web项目(idea)

    web项目创建 Web项目开发需要导入许多的第三方jar包,用Maven创建web项目就可以将这一操作免去. 本项目是在idea下创建的,idea自带Maven,如果还未了解Maven的同学可以看上一 ...

  8. java 使用openoffice将doc、docx、ppt、pptx等转换pdf格式文件

    软件的安装与配置 链接: 官网 / C**N / 毒盘:提取码k47b 有能力的大佬可以选择c**n下载支持支持,官网下载可能有点慢 Windows下安装配置 下载安装包,安装到硬盘(这个就不截图了吧 ...

  9. 【转载】 t-SNE是什么? —— 使用指南

    原文地址: https://www.cnblogs.com/LuckBelongsToStrugglingMan/p/14161405.html 转者前言: 该文相当于一个  t-SNE  使用指南, ...

  10. 为什么被要求避免使用orcid

    前段时间收到了最高机构的通知,要求不要使用orcid,并对之前使用的情况进行自查.得到这个通知,我其实还是蛮无感的,毕竟我一篇论文也没法过,而且也没有用过这个orcid,虽然我自己也是有这个的. 关于 ...