rabbitmq死信队列处理

创建私信队列并绑定

# 死信交换机配置 以直连交换机为列
my:
  exchangeNormalName: exchange.normal.a   #正常交换机
  queueNormalName: queue.normal.a         #正常队列
  exchangeDlxName: exchange.dlx.a         #死信交换机
  queueDlxName: queue.dlx.a               #死信队列

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * 死信交换机
 */
@Configuration
public class DeadLetterExchangeConfig {
    @Value("${my.exchangeNormalName}")
    private String exchangeNormalName;

    @Value("${my.queueNormalName}")
    private String queueNormalName;

    @Value("${my.exchangeDlxName}")
    private String exchangeDlxName;

    @Value("${my.queueDlxName}")
    private String queueDlxName;

    /**
     * 正常交换机
     * @return
     */
    @Bean
    public DirectExchange normalExchange(){
        return ExchangeBuilder.directExchange(exchangeNormalName).build();
    }

    /**
     * 正常队列
     * @return
     */
    @Bean
    public Queue normalQueue(){
        Map<String, Object> arguments = new HashMap<>();
        //重点:设置这两个参数
        //设置队列的死信交换机
        arguments.put("x-dead-letter-exchange",exchangeDlxName);
        //设置死信路由key,要跟死信交换机和死信队列绑定的路由key一致
        arguments.put("x-dead-letter-routing-key","error");
        return new Queue(queueNormalName,true,false,false,arguments);
    }

    /**
     * 正常交换机和正常队列绑定
     * @param normalExchange
     * @param normalQueue
     * @return
     */
    @Bean
    public Binding bingNormal(DirectExchange normalExchange,Queue normalQueue){
        return BindingBuilder.bind(normalQueue).to(normalExchange).with("order");
    }


    /**
     * 死信交换机
     * @return
     */
    @Bean
    public DirectExchange dlxExchange(){
        return ExchangeBuilder.directExchange(exchangeDlxName).build();
    }

    /**
     * 死信队列
     * @return
     */
    @Bean
    public Queue dlxQueue(){
        return new Queue(queueDlxName,true,false,false);

    }

    /**
     * 死信交换机和死信队列绑定
     * @param dlxExchange
     * @param dlxQueue
     * @return
     */
    @Bean
    public Binding bindDlx(DirectExchange dlxExchange,Queue dlxQueue){
        return BindingBuilder.bind(dlxQueue).to(dlxExchange).with("error");
    }


}

消息发送:

/**
     * 死信交换机-直连交换机 消息过期
     */
    @Test
    public void sendDirectMessage2() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "test message, hello!";
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        Map<String, Object> map = new HashMap<>();
        map.put("messageId", messageId);
        map.put("messageData", messageData);
        map.put("createTime", createTime);

        //给消息设置过期时间
        MessagePostProcessor messagePostProcessor = message -> {
            //20秒后过期
            message.getMessageProperties().setExpiration("20000");
            message.getMessageProperties().setContentEncoding("UTF-8");
            //持久化
            message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
            //非持久化
            //message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT);
            return message;
        };
        //将消息携带绑定键值:TestDirectRouting 发送到交换机TestDirectExchange
        rabbitTemplate.convertAndSend("exchange.normal.a", "order", map, messagePostProcessor);
    }

方案 1:手动重新投递消息

适用场景: 偶发性错误(如临时依赖服务不可用),消息可重试。

操作步骤:

  1. 从死信队列中取出消息:通过管理界面或代码手动消费死信队列。
  2. 修复消息内容:调整消息体或头信息(如修复格式错误)。
  3. 重新发布到原队列:

import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;
import java.util.Map;


/**
 * 死信队列消费  手动重新投递消息
 */
@Component
public class DlxReceiver {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 监听死信队列
    @RabbitListener(queues = "queue.dlx.a",ackMode = "MANUAL")
    public void handleDlqMessage(Map<String,Object> messageBody,Channel channel, Message message) throws IOException {
        try {
            String originalExchange = getOriginalExchange(message);
            String originalRoutingKey = getOriginalRoutingKey(message);

            // 重新发布到原队列的交换机
            rabbitTemplate.convertAndSend(
                    originalExchange,
                    originalRoutingKey,
                    messageBody,
                    msg -> {
                        // 移除死信标记(防止循环进入 DLQ)
                        msg.getMessageProperties().getHeaders().remove("x-death");
                        return msg;
                    }
            );
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            System.out.println("消息重新投递成功: " + messageBody);
        } catch (Exception e) {
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),
                    false, true);
            System.err.println("消息重新投递失败: " + e.getMessage());
        }
    }

    // 从消息头中提取原始交换机名称
    private String getOriginalExchange(Message message) {
        List<Map<String,Object>> list =  message.getMessageProperties()
                .getHeader("x-death");
        for (Map<String,Object> map : list){
            return map.get("exchange").toString();
        }
        return message.getMessageProperties().getHeader("x-last-death-exchange").toString();
    }

    // 从消息头中提取原始路由键
    private String getOriginalRoutingKey(Message message) {
       List<Map<String,Object>> list =  message.getMessageProperties()
                .getHeader("x-death");
       for (Map<String,Object> map : list){
           List<String> routing_keys_list = (List<String>) map.get("routing-keys");
           return routing_keys_list.get(0);
       }
        return "";
    }



}

方案 2:自动化重试机制

适用场景: 需自动处理大量死信消息。

实现方式:

1、消费者监听死信队列:编写消费者处理死信消息,按策略重试。

2、重试次数限制:通过 x-retry-count 头信息控制最大重试次数,避免无限循环。

死信队列


import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;
import java.util.Map;


/**
 * 死信队列消费  自动化重试机制
 */
@Component
public class DlxReceiver_2 {
    private static final int MAX_RETRIES = 3; // 最大重试次数

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 监听死信队列,执行重试逻辑
    @RabbitListener(queues = "queue.dlx.a",ackMode = "MANUAL")
    public void handleDlqMessage(Map<String,Object> messageBody,Channel channel, Message message) throws IOException {
        try {
            // 获取消息体和元数据
            Map<String, Object> headers = message.getMessageProperties().getHeaders();
            String originalExchange = getOriginalExchange(message);
            String originalRoutingKey = getOriginalRoutingKey(message);
            // 解析重试次数
            int retryCount = (int) headers.getOrDefault("x-retry-count", 0);

            if (retryCount >= MAX_RETRIES) {
                // 超过最大重试次数,归档消息
                archiveMessage(messageBody);
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                System.out.println("消息已归档: " + messageBody);
                return;
            }

            // 重试:更新重试次数并重新投递到原队列
            headers.put("x-retry-count", retryCount + 1);
            rabbitTemplate.convertAndSend(
                    originalExchange,
                    originalRoutingKey, // 直接发送到原队列(使用默认交换机)
                    messageBody,
                    msg -> {
                        msg.getMessageProperties().getHeaders().putAll(headers);
                        return msg;
                    }
            );
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            System.out.println("重试投递 (" + (retryCount + 1) + "/" + MAX_RETRIES + "): " + messageBody);
        } catch (Exception e) {
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),
                    false, true);
            System.err.println("重试处理失败: " + e.getMessage());
        }
    }

    // 归档消息(模拟实现)
    private void archiveMessage(Map<String,Object> message) {
        // 实际可存储到数据库、文件或发送到归档队列
        System.out.println("归档消息: " + message);
    }

    // 从消息头中提取原始交换机名称
    private String getOriginalExchange(Message message) {
        List<Map<String,Object>> list =  message.getMessageProperties()
                .getHeader("x-death");
        for (Map<String,Object> map : list){
            return map.get("exchange").toString();
        }
        return message.getMessageProperties().getHeader("x-last-death-exchange").toString();
    }

    // 从消息头中提取原始路由键
    private String getOriginalRoutingKey(Message message) {
        List<Map<String,Object>> list =  message.getMessageProperties()
                .getHeader("x-death");
        for (Map<String,Object> map : list){
            List<String> routing_keys_list = (List<String>) map.get("routing-keys");
            return routing_keys_list.get(0);
        }
        return "";
    }

}

正常消息消费:


import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;
import java.util.Map;

@Component
@RabbitListener(queues = "queue.normal.a",ackMode = "MANUAL")//监听的队列名称 TestDirectQueue
public class DirectReceiver_3 {

    @RabbitHandler
    public void process(Map<String,Object> testMessage,Channel channel, Message message) throws IOException {


        try {
            /**
             * TODO 业务处理
             */
            System.out.println("get msg2 success msg = "+testMessage);
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),
                    false, false);
        } catch (Exception e) {
            //消费者处理出了问题,需要告诉队列信息消费失败
            /**
             * 拒绝确认消息:
* channel.basicNack(long deliveryTag, boolean multiple, boolean requeue) ;
* deliveryTag:该消息的index
* multiple:是否批量.true:将一次性拒绝所有小于deliveryTag的消息。
* requeue:被拒绝的是否重新入队列
*/
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false); /** * 拒绝一条消息:
* channel.basicReject(long deliveryTag, boolean requeue);
* deliveryTag:该消息的index
* requeue:被拒绝的是否重新入队列 */
//channel.basicReject(message.getMessageProperties().getDeliveryTag(), true); /** * TODO: 业务处理 */ System.err.println("get msg2 failed msg = "+testMessage); } } }

死信消息消费:


import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;
import java.util.Map;


/**
 * 死信队列消费  自动化重试机制
 */
@Component
public class DlxReceiver_2 {
    private static final int MAX_RETRIES = 3; // 最大重试次数

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 监听死信队列,执行重试逻辑
    @RabbitListener(queues = "queue.dlx.a",ackMode = "MANUAL")
    public void handleDlqMessage(Map<String,Object> messageBody,Channel channel, Message message) throws IOException {
        try {
            // 获取消息体和元数据
            Map<String, Object> headers = message.getMessageProperties().getHeaders();
            String originalExchange = getOriginalExchange(message);
            String originalRoutingKey = getOriginalRoutingKey(message);
            // 解析重试次数
            int retryCount = (int) headers.getOrDefault("x-retry-count", 0);

            if (retryCount >= MAX_RETRIES) {
                // 超过最大重试次数,归档消息
                archiveMessage(messageBody);
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                System.out.println("消息已归档: " + messageBody);
                return;
            }

            // 重试:更新重试次数并重新投递到原队列
            headers.put("x-retry-count", retryCount + 1);
            rabbitTemplate.convertAndSend(
                    originalExchange,
                    originalRoutingKey, // 直接发送到原队列(使用默认交换机)
                    messageBody,
                    msg -> {
                        msg.getMessageProperties().getHeaders().putAll(headers);
                        return msg;
                    }
            );
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            System.out.println("重试投递 (" + (retryCount + 1) + "/" + MAX_RETRIES + "): " + messageBody);
        } catch (Exception e) {
            channel.basicNack(message.getMessageProperties().getDeliveryTag(),
                    false, true);
            System.err.println("重试处理失败: " + e.getMessage());
        }
    }

    // 归档消息(模拟实现)
    private void archiveMessage(Map<String,Object> message) {
        // 实际可存储到数据库、文件或发送到归档队列
        System.out.println("归档消息: " + message);
    }

    // 从消息头中提取原始交换机名称
    private String getOriginalExchange(Message message) {
        List<Map<String,Object>> list =  message.getMessageProperties()
                .getHeader("x-death");
        for (Map<String,Object> map : list){
            return map.get("exchange").toString();
        }
        return message.getMessageProperties().getHeader("x-last-death-exchange").toString();
    }

    // 从消息头中提取原始路由键
    private String getOriginalRoutingKey(Message message) {
        List<Map<String,Object>> list =  message.getMessageProperties()
                .getHeader("x-death");
        for (Map<String,Object> map : list){
            List<String> routing_keys_list = (List<String>) map.get("routing-keys");
            return routing_keys_list.get(0);
        }
        return "";
    }

}

rabbitmq死信队列处理_第1张图片

方案 3:人工介入处理

适用场景: 消息涉及业务关键逻辑,需人工审核。

工具支持:

1、管理界面:通过 RabbitMQ 管理界面直接查看消息内容并导出。

2、日志系统:将死信消息记录到日志或数据库供人工分析。

你可能感兴趣的:(java-rabbitmq,rabbitmq,java)