rabbitmq死信交换机,死信队列使用

背景

对于核心业务需要保证消息必须正常消费,就必须考虑消费失败的场景,rabbitmq提供了以下三种消费失败处理机制

  1. 直接reject,丢弃消息(默认)
  2. 返回nack,消息重新入队列
  3. 将失败消息投递到指定的交换机
    对于核心业务,第一种方法显然不可接受,第二种方法如果代码有异常导致消费一直失败就会出现不断失败重新入队列的死循环问题,较好的方案是3,待消费失败问题修复后将消息从死信队列取出发回原队列重新消费。

实现

  • rabbit版本
xml 复制代码
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-amqp</artifactId>
  <version>2.6.3</version>
</dependency>
  • 配置死信交换机,路由,队列

  • 配置延迟消息业务队列消费失败投递到死信队列
java 复制代码
@Bean("orderCloseQueue")
    public Queue orderCloseQueue() {
        return QueueBuilder.durable(OrderRabbitConstants.ORDER_CLOSE_QUEUE)
            .deadLetterExchange(RabbitMqConstants.DEAD_LETTER_EXCHANGE)
            .deadLetterRoutingKey(RabbitMqConstants.DEAD_LETTER_ROUTING_KEY)
            .build();
    }
  • 配置手动返回ACK
java 复制代码
@Bean(name = {"manualContainerFactory"})
public SimpleRabbitListenerContainerFactory manualContainerFactory(@Qualifier("connectionFactory") ConnectionFactory connectionFactory) {
	SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
	this.manualFactoryConfigurer.configure(factory, connectionFactory);
	factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
	factory.setDefaultRequeueRejected(this.enableRequeueRejected);
	if (this.enableConsumers) {
		factory.setConcurrentConsumers(this.concurrentConsumers);
		factory.setMaxConcurrentConsumers(this.maxConcurrentConsumers);
		factory.setPrefetchCount(this.prefetchCount);
	}
	return factory;
}
  • 业务队列消息消费模拟失败
java 复制代码
@RabbitListener(queues = OrderRabbitConstants.ORDER_CLOSE_QUEUE, containerFactory = "manualContainerFactory")
    public void consumerCloseOrder(Message message, Channel channel) throws IOException {
        String orderCode = new String(message.getBody(), CharsetUtil.UTF_8);
        String messageId = message.getMessageProperties().getMessageId();
        log.info("收到MQ messageId[{}],订单号[{}]", messageId, orderCode);
        if (true) {
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
            return;
        }
    }
  • 效果



    可以看到死信队列dead.letter.queue已经正常收到死信消息
  • 编写逻辑将死信消费推回原队列
java 复制代码
for (int i = 0; i < 10_000; i++) {
     Message message = rabbitTemplate.receive(RabbitMqConstants.DEAD_LETTER_QUEUE);
     if (message == null) {
         return String.format("完成%d条", i);
     }
     log.info("拉取死信消息:[{}]", message);
     try {
         Map<String, Object> headers = message.getMessageProperties().getHeaders();
         Map<String, Object> deathMap = ((List<Map<String, Object>>) headers.get("x-death")).get(0);
         String exchange = deathMap.get("exchange").toString();
         String routingKey = ((List) deathMap.get("routing-keys")).get(0).toString();
         rabbitTemplate.send(exchange, routingKey, message);
     } catch (Exception ex) {
         log.error("消费死信消息失败", ex);
         rabbitTemplate.send(RabbitMqConstants.DEAD_LETTER_EXCHANGE, RabbitMqConstants.DEAD_LETTER_ROUTING_KEY, message);
         return "重入队列异常";
     }
 }
  • 重推回业务队列效果

相关推荐
茶杯梦轩2 天前
从零起步学习RabbitMQ || 第三章:RabbitMQ的生产者、Broker、消费者如何保证消息不丢失(可靠性)详解
分布式·后端·面试
回家路上绕了弯3 天前
深入解析Agent Subagent架构:原理、协同逻辑与实战落地指南
分布式·后端
用户8307196840823 天前
Spring Boot 集成 RabbitMQ :8 个最佳实践,杜绝消息丢失与队列阻塞
spring boot·后端·rabbitmq
用户8307196840825 天前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者6 天前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
初次攀爬者8 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
让我上个超影吧10 天前
消息队列——RabbitMQ(高级)
java·rabbitmq
塔中妖10 天前
Windows 安装 RabbitMQ 详细教程(含 Erlang 环境配置)
windows·rabbitmq·erlang
断手当码农10 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者10 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端