RabbitMQ 11 死信队列
死信队列
概述
消息队列中的数据,如果迟迟没有消费者来处理,就会一直占用消息队列的空间。
比如抢车票的场景,用户下单高铁票之后,会进行抢座,然后再进行付款,但是如果用户下单之后并没有及时的付款,这张票不可能一直让这个用户占用着,因为这样别人就买不到这张票了,所以会在一段时间后超时,让这张票可以继续被其他人购买。
这时,就可以使用死信队列,将那些用户超时未付款的或是用户主动取消的订单,进行进一步的处理。

那么如何构建这样的一种使用模式呢?实际上本质就是一个死信交换机+死信队列。
当正常队列中的消息被判定为死信时,会被发送到对应的死信交换机,然后再通过交换机发送到死信队列中,死信队列也有对应的消费者去处理消息。
判定为死信一般是3种情况:
- 消息被拒绝(
basic.reject/basic.nack),并且requeue = false。 - 消息超时未消费。
- 消息队列达到最大长度。
产生死信
消息被拒绝
在配置类中创建一个新的死信交换机和死信队列,并进行绑定。
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* RabbitMQ配置类
*/
@Configuration
public class RabbitMqConfig { ... /**
* 定义消息队列
* @return 消息队列对象
*/
@Bean("testQueue")
public Queue queue(){
return QueueBuilder
// 非持久化类型
.nonDurable("test_springboot")
// 指定死信交换机
.deadLetterExchange("dl.direct")
// 指定死信RoutingKey
.deadLetterRoutingKey("dl_test_springboot_key")
.build();
} /**
* 构建死信交换机
* @return 死信交换机
*/
@Bean
public Exchange dlExchange(){
// 创建一个新的死信交换机
return ExchangeBuilder.directExchange("dl.direct").build();
} /**
* 构建死信队列
* @return 死信队列
*/
@Bean
public Queue dlQueue(){
return QueueBuilder
.nonDurable("dl_test_springboot")
.build();
} /**
* 死信交换机和死信队列绑定
* @param exchange 死信交换机
* @param queue 死信队列
* @return 绑定对象
*/
@Bean
public Binding dlBinding(@Qualifier("dlExchange") Exchange exchange,
@Qualifier("dlQueue") Queue queue){
return BindingBuilder
.bind(queue)
.to(exchange)
.with("dl_test_springboot_key")
.noargs();
} ... }
监听正常队列和死信队列消息。
import cn.codesail.rabbitmq.entity.User;
import com.rabbitmq.client.Channel;
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; /**
* 直连队列监听器
*/
@Component
public class DirectListener { /**
* 监听正常队列消息
*/
@RabbitListener(queues = "test_springboot", messageConverter = "jackson2JsonMessageConverter")
public void receiver(Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, User user) throws Exception {
// 拒绝消息。第二个参数为true则消息返回队列,第二个参数为false则消息不返回队列,成为死信
channel.basicReject(deliveryTag, false);
System.out.println("正常队列接收到消息:" + user);
} /**
* 监听死信队列消息
*/
@RabbitListener(queues = "dl_test_springboot", messageConverter = "jackson2JsonMessageConverter")
public void receiverDl(User user) {
System.out.println("死信队列接收到消息:" + user);
}
}
正常队列消息的监听种拒绝了消息,且不返回队列,成为了死信,就会被死信队列的监听接收到。
删除原队列。删除了原队列才能创建与死信队列绑定的队列。
实现生产者。
import cn.codesail.rabbitmq.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest
class RabbitMqSpringBootTests { /**
* RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
*/
@Autowired
private RabbitTemplate rabbitTemplate; /**
* 生产者
*/
@Test
void producer() { // 发送Json消息
User user = new User();
user.setName("张三");
user.setAge(18);
rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", user);
} }
启动生产者发送消息:

可以看到,死信队列接收到了消息。
消息超时未消费
设定队列TTL值。
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* RabbitMQ配置类
*
* @author CodeSail
*/
@Configuration
public class RabbitMqConfig { ... /**
* 定义消息队列
* @return 消息队列对象
*/
@Bean("testQueue")
public Queue queue(){
return QueueBuilder
// 非持久化类型
.nonDurable("test_springboot")
// 指定死信交换机
.deadLetterExchange("dl.direct")
// 指定死信RoutingKey
.deadLetterRoutingKey("dl_test_springboot_key")
// 如果5秒没处理,就自动删除
.ttl(5000)
.build();
} ...
}取消正常队列监听。
import cn.codesail.rabbitmq.entity.User;
import com.rabbitmq.client.Channel;
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; /**
* 直连队列监听器
*/
@Component
public class DirectListener { /**
* 监听正常队列消息
*/
// @RabbitListener(queues = "test_springboot", messageConverter = "jackson2JsonMessageConverter")
// public void receiver(Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, User user) throws Exception {
// // 拒绝消息。第二个参数为true则消息返回队列,第二个参数为false则消息不返回队列,成为死信
// channel.basicReject(deliveryTag, false);
// System.out.println("正常队列接收到消息:" + user);
// } /**
* 监听死信队列消息
*/
@RabbitListener(queues = "dl_test_springboot", messageConverter = "jackson2JsonMessageConverter")
public void receiverDl(User user) {
System.out.println("死信队列接收到消息:" + user);
}
}实现生产者。
import cn.codesail.rabbitmq.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.TimeUnit; @SpringBootTest
class RabbitMqSpringBootTests { /**
* RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
*/
@Autowired
private RabbitTemplate rabbitTemplate; /**
* 生产者
*/
@Test
void producer() throws InterruptedException { // 发送Json消息
User user = new User();
user.setName("张三");
user.setAge(18);
rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", user);
} }
删除原队列。删除了原队列才能创建与死信队列绑定的设定了TTL的队列。
启动服务,监听消息。

启动生产者发送消息,等待5秒:

可以看到,死信队列接收到了消息。
消息队列达到最大长度
设置队列最大消息长度。
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* RabbitMQ配置类
*/
@Configuration
public class RabbitMqConfig { ... /**
* 定义消息队列
* @return 消息队列对象
*/
@Bean("testQueue")
public Queue queue(){
return QueueBuilder
// 非持久化类型
.nonDurable("test_springboot")
// 指定死信交换机
.deadLetterExchange("dl.direct")
// 指定死信RoutingKey
.deadLetterRoutingKey("dl_test_springboot_key")
// 最大长度设定为3
.maxLength(3)
.build();
} ... }
取消正常队列监听。
import cn.codesail.rabbitmq.entity.User;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component; /**
* 直连队列监听器
*/
@Component
public class DirectListener { /**
* 监听正常队列消息
*/
// @RabbitListener(queues = "test_springboot", messageConverter = "jackson2JsonMessageConverter")
// public void receiver(User user) {
// System.out.println("正常队列接收到消息:" + user);
// } /**
* 监听死信队列消息
*/
@RabbitListener(queues = "dl_test_springboot", messageConverter = "jackson2JsonMessageConverter")
public void receiverDl(User user) {
System.out.println("死信队列接收到消息:" + user);
}
}
删除原队列。删除了原队列才能创建与死信队列绑定的设定了最大长度的队列。
定义生产者。
import cn.codesail.rabbitmq.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.TimeUnit; @SpringBootTest
class RabbitMqSpringBootTests { /**
* RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
*/
@Autowired
private RabbitTemplate rabbitTemplate; /**
* 生产者
*/
@Test
void producer() throws InterruptedException { for (int i = 0; i < 4; i++) {
User user = new User();
user.setName("张三" + i);
user.setAge(18);
rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", user);
}
} }
启动生产者发送消息。

可以看到,队列的第一个元素被挤出成为了死信。
队列就类似于一个管道,当管道的人占满了,最后进去的人就会把最前面的人挤出去。
- 环境
- JDK 17.0.6
- Maven 3.6.3
- SpringBoot 3.0.4
- spring-boot-starter-amqp 3.0.4
- jackson-databind 2.14.2
- 参考
RabbitMQ 11 死信队列的更多相关文章
- RabbitMQ实战-死信队列
RabbitMQ死信队列 场景说明 代码实现 简单的Util 生产者 消费者 场景说明 场景: 当队列的消息未正常被消费时,如何解决? 消息被拒绝并且不再重新投递 消息超过有效期 队列超载 方案: 未 ...
- RabbitMQ配置死信队列
死信队列 消息传输过程中难免会产生一些无法及时处理的消息,这些暂时无法处理的消息有时候也是需要被保留下来的,于是这些无法被及时处理的消息就变成了死信. 既然需要保留这些死信,那么就需要一个容器来存储它 ...
- RabbitMQ之死信队列
1:何为死信队列 死信队列也是一个正常的队列,可以被消费. 但是,死信队列的消息来源于其他队列的转发. 2:如何触发死信队列 1:消息超时 2:队列长度达到极限 3:消息被拒绝消费,并不再重进队列,且 ...
- RabbitMQ死信队列另类用法之复合死信
前言 在业务开发过程中,我们常常需要做一些定时任务,这些任务一般用来做监控或者清理任务,比如在订单的业务场景中,用户在创建订单后一段时间内,没有完成支付,系统将自动取消该订单,并将库存返回到商品中,又 ...
- 【RabbitMQ】一文带你搞定RabbitMQ死信队列
本文口味:爆炒鱿鱼 预计阅读:15分钟 一.说明 RabbitMQ是流行的开源消息队列系统,使用erlang语言开发,由于其社区活跃度高,维护更新较快,性能稳定,深得很多企业的欢心(当然,也包括我 ...
- netcore下死RabbitMQ队列、死信队列、延时队列及小应用
关于安装rabbitmq这里一笔掠过了. 下面进入正题: 1.新建aspnetcorewebapi空项目,NormalQueue,删除controllers文件夹已经无关的文件,这里为了偷懒不用con ...
- 《RabbitMQ》什么是死信队列
一 什么是死信队列 当一条消息在队列中出现以下三种情况的时候,该消息就会变成一条死信. 消息被拒绝(basic.reject / basic.nack),并且requeue = false 消息TTL ...
- rabbitmq实现延时队列(死信队列)
基于队列和基于消息的TTL TTL是time to live 的简称,顾名思义指的是消息的存活时间.rabbitMq可以从两种维度设置消息过期时间,分别是队列和消息本身. 队列消息过期时间-Per-Q ...
- RabbitMQ 死信队列 延时
package com.hs.services.config; import java.util.HashMap; import java.util.Map; import org.springfra ...
- 关于 RabbitMQ 的 Dead-Letters-Queue “死信队列”
来自一个队列的消息可以被当做‘死信’,即被重新发布到另外一个“exchange”去,这样的情况有: 消息被拒绝 (basic.reject or basic.nack) 且带 requeue=fa ...
随机推荐
- 【Azure 应用服务】基于Azure的CI/CD工具链部署App Service
问题描述 在中国区Azure中,App Service是否支持CI/CD工具部署呢? Windows 和Linux两个系统都是同样的方法吗? 问题解答 目前中国区Azure支持Windows 和 Li ...
- Nebula Graph 源码解读系列 | Vol.06 MATCH 中变长 Pattern 的实现
目录 问题分析 定长 Pattern 变长 Pattern 与变长 Pattern 的组合 执行计划 拓展一步 拓展多步 保存路径 变长拼接 总结 MATCH 作为 openCypher 语言的核心, ...
- 有n步台阶,一次只能上1步或2步,共有多少种走法
循环迭代: 1 public class steps { 2 public int js(int n) { 3 int one = 2; //初始化为第三级台阶最后跨一步的走法 4 int two ...
- Educational Codeforces Round 135 (Rated for Div. 2)C. Digital Logarithm(思维)
目录 题目链接 题意 题解 代码 题目链接 C. Digital Logarithm 题意 给两个长度位\(n\)的数组\(a\).\(b\),一个操作\(f\) 定义操作\(f\)为,\(a[i]= ...
- Hello 2024C. Grouping Increases(贪心)
我们只需要记录每个数结尾的数是多少(有点最长上升子序列的味道) 这种子序列的题目很多都是这样的,因为不需要连续很多时候我们只记录最后一个元素是多少. \(记s为较大子序列结尾当前的数,t为较小子序列结 ...
- 【预训练语言模型】BERT原理解析、常见问题和微调实战
一.BERT原理 1.概述 背景:通过在大规模语料上预训练语言模型,可以显著提高其在NLP下游任务的表现. 动机:限制模型潜力的主要原因在于现有模型使用的都是单向的语言模型 ...
- MinimalApis自动注册
前言 在Asp.Net Core 6 推出了最小 Api(MinimalApis)来简化WebApi的开发,在前后端分离的趋势下越来越多的后端服务只提供Api接口,但是用Controller的开发模式 ...
- 一个简单的HTTP服务器的实现
我们继续我们的HTTP服务器的实现(使用别的代码来实现), 这个HTTP服务器的实现,我们主要就是关注TCP服务器中的recv还有send的处理. 首先,看一下HTTP,我们在用浏览器访问我们的TCP ...
- day28--Java泛型01
Java泛型01 1.泛型的理解和好处 看一个需求: 请编写程序,在ArrayList中添加三个Dog对象 Dog对象含有name和age,并输出name和age(要求使用getXXX()) 先用传统 ...
- [leetcode 496. 下一个更大元素 I] 单调栈
单调栈的写法: import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; import java.u ...