RabbitMQ死信交换机全解析:消息何时会进入死信队列?

RabbitMQ死信交换机全解析:消息何时会进入死信队列?

    • 1. 什么是死信交换机?
    • 2. 消息进入死信交换机的三种情况
      • 2.1 情况一:消息被消费者拒绝
      • 2.2 情况二:消息过期(TTL)
      • 2.3 情况三:队列达到最大长度
    • 3. 完整配置示例
    • 4. 死信消息的原始信息
    • 5. 完整流程图
    • 6. 实际应用场景
    • 7. 常见问题

🌺The Begin🌺点点关注,收藏不迷路🌺

在RabbitMQ中,死信交换机(DLX)是处理异常消息的核心机制。当消息变成“死信”时,会被自动路由到死信交换机,进而进入死信队列供后续排查或补偿处理。本文将全面解析消息进入死信交换机的三种情况。

1. 什么是死信交换机?

死信指无法被正常消费的消息。消息变成死信后,不会直接被丢弃,而是被发送到预先指定的死信交换机,最终进入死信队列

消息变成死信

生产者

普通交换机

普通队列
配置了DLX

死信交换机

死信队列

死信消费者

2. 消息进入死信交换机的三种情况

死信来源

消息被拒绝

basic.reject

basic.nack

requeue=false

消息过期

队列TTL

消息TTL

队列达到最大长度

最大消息条数

最大字节数

2.1 情况一:消息被消费者拒绝

消费者调用basic.rejectbasic.nack,且requeue=false时,消息进入死信交换机。

@Component
public class MessageConsumer {
    @RabbitListener(queues = "normal.queue")
    public void handleMessage(Message message, Channel channel) throws IOException {
        try {
            // 业务处理
            processMessage(message);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
            // ❌ 拒绝且不重新入队 → 进入死信
            channel.basicNack(
                message.getMessageProperties().getDeliveryTag(),
                false,  // 不批量
                false   // requeue = false,不进死信
            );
        }
    }
}

requeue=falsetrue的区别:

参数 行为 是否进死信
requeue=true 重新放回队列尾部
requeue=false 进入死信交换机(如配置了DLX)

2.2 情况二:消息过期(TTL)

TTL可通过队列级别消息级别设置,取较小值。消息过期后被移除队列,进入死信交换机。

队列级别TTL:队列中所有消息统一过期时间

@Bean
public Queue normalQueue() {
    return QueueBuilder.durable("normal.queue")
        .ttl(10000)  // 10秒过期
        .deadLetterExchange("dead.exchange")
        .deadLetterRoutingKey("dead.routingkey")
        .build();
}

消息级别TTL:每条消息单独设置过期时间

public void sendMessageWithTTL(String message, int ttlMillis) {
    MessageProperties props = new MessageProperties();
    props.setExpiration(String.valueOf(ttlMillis));
    Message msg = MessageBuilder.withBody(message.getBytes())
        .andProperties(props)
        .build();
    rabbitTemplate.convertAndSend("normal.exchange", "normal.key", msg);
}

2.3 情况三:队列达到最大长度

队列设置了最大长度或最大字节数,超出限制时,头部消息变成死信(取决于溢出策略)。

@Bean
public Queue normalQueue() {
    return QueueBuilder.durable("normal.queue")
        .maxLength(10)  // 最多10条消息
        .deadLetterExchange("dead.exchange")
        .build();
}

溢出行为默认删除头部消息,这些被删除的消息会进入死信交换机。可通过x-overflow参数配置:drop-head(默认)或reject-publish

3. 完整配置示例

@Configuration
public class DeadLetterConfig {
    public static final String NORMAL_EXCHANGE = "normal.exchange";
    public static final String NORMAL_QUEUE = "normal.queue";
    public static final String NORMAL_KEY = "normal.key";
    public static final String DEAD_EXCHANGE = "dead.exchange";
    public static final String DEAD_QUEUE = "dead.queue";
    public static final String DEAD_KEY = "dead.key";
    @Bean
    public DirectExchange normalExchange() {
        return new DirectExchange(NORMAL_EXCHANGE);
    }
    @Bean
    public DirectExchange deadExchange() {
        return new DirectExchange(DEAD_EXCHANGE);
    }
    @Bean
    public Queue normalQueue() {
        return QueueBuilder.durable(NORMAL_QUEUE)
            .ttl(10000)  // 10秒过期
            .maxLength(1000)  // 最多1000条
            .deadLetterExchange(DEAD_EXCHANGE)
            .deadLetterRoutingKey(DEAD_KEY)
            .build();
    }
    @Bean
    public Queue deadQueue() {
        return QueueBuilder.durable(DEAD_QUEUE).build();
    }
    @Bean
    public Binding normalBinding() {
        return BindingBuilder.bind(normalQueue())
            .to(normalExchange())
            .with(NORMAL_KEY);
    }
    @Bean
    public Binding deadBinding() {
        return BindingBuilder.bind(deadQueue())
            .to(deadExchange())
            .with(DEAD_KEY);
    }
}

4. 死信消息的原始信息

消息进入死信队列后,会增加以下Header:

@Component
public class DeadLetterConsumer {
    @RabbitListener(queues = "dead.queue")
    public void handleDeadLetter(Message message) {
        MessageProperties props = message.getMessageProperties();
        // 获取死信原因
        String reason = props.getHeader("x-death").toString();
        // 输出类似:
        // [{
        //   "reason": "rejected",      // 死信原因
        //   "queue": "normal.queue",   // 原队列
        //   "time": 1699876543212,     // 死信时间
        //   "exchange": "normal.exchange",
        //   "routing-keys": ["normal.key"]
        // }]
        // 原始消息内容
        String originalBody = new String(message.getBody());
        // 记录死信,进行补偿处理
        saveToDatabase(originalBody, reason);
    }
}

死信原因类型:

  • rejected:消费者拒绝且requeue=false
  • expired:消息TTL过期
  • maxlen:队列达到最大长度

5. 完整流程图

正常消费

消费者拒绝
requeue=false

消息过期

队列满
头部溢出

消息到达普通队列

消息状态

消费者确认ACK

结束

进入死信交换机

死信交换机路由

死信队列

死信消费者

补偿处理
记录日志/人工介入

6. 实际应用场景

场景 触发方式 处理方案
消息格式错误 消费者拒绝 进入死信,记录日志,人工修复
超时未支付订单 设置TTL=30分钟 进入死信,自动取消订单
流量高峰积压 队列长度限制 进入死信,转移到慢队列
重试次数耗尽 消费者拒绝 进入死信,人工介入

7. 常见问题

Q1:没有配置死信交换机,死信会怎样?

A:消息被直接丢弃。生产环境务必为重要队列配置DLX。

Q2:死信队列的消息还能再次进入死信吗?

A:会。如果死信队列也配置了DLX,消息再次变成死信会进入下一个死信队列,可能形成循环,需避免。

Q3:如何避免死信队列无限增长?

A:设置死信队列的TTL和最大长度,或定期清理。


📌 一句话总结

消息进入死信交换机有三种情况:消费者拒绝且不重新入队、TTL过期、队列满溢出。死信是RabbitMQ的"异常处理机制",而非错误。

在这里插入图片描述

🌺The End🌺点点关注,收藏不迷路🌺
© 版权声明

相关文章