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
- 参考
天河有尽身作涯,星海无边前是岸。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
2022-04-11 IO流