RabbitMQ Learning (5) -- Dead Letter Queue

Dead Letter Exchanges : Dead Letter Exchanges — RabbitMQ

1. Concept

DLX, the full name of Dead Letter Exchanges, can be called a dead letter exchange, and some people call it a dead letter mailbox. When a message becomes a dead message in a queue, it can be resent to another switch. This switch is DLX, and the queue bound to DLX is called a dead letter queue .

Application scenario: In order to ensure that the message data of the order business is not lost, the dead letter queue mechanism of RabbitMQ needs to be used. When an exception occurs in message consumption, the message will be put into the dead letter queue. Another example: After the user successfully places an order in the mall and clicks to pay, it will automatically become invalid if the payment is not made within the specified time.

2. The source of dead letters

Reasons why a message becomes a dead letter:

  • The consumer negatively acknowledges the message with basic.rejector basic.nackwith requeuethe parameter set to false. (message rejected)

  • Message TTL expired. (message expired)

  • A message was dropped because its queue reached its maximum length (queue was full)

3. Dead letter combat

1) Code Architecture Diagram

The producer only cares which exchange I send the message to, not which queue you forward it to, the queue is bound in the consumer statement

The producer is only responsible for producing messages and sending them to the switch, and any failures in the rest of the messages are caused by consumers

The dead letter queue is actually an ordinary queue bound to the dead letter switch, and the dead letter switch is just an ordinary switch, but it is a switch specially used to process dead letters

Consumer 01 wants to declare normal_exchangeand dead_exchangeand normal_queueand and dead_queuepairs of columns

insert image description here

"queue" – 队列的名称
"durable"——如果我们声明一个持久队列,则为真(该队列将在服务器重启后继续存在)
"exclusive" – 如果我们声明一个独占队列,则为 true(仅限于此连接)
"autoDelete" – 如果我们声明一个自动删除队列,则为 true(服务器将在不再使用时将其删除)
"arguments" – 队列的其他属性(构造参数)

Queue.DeclareOk queueDeclare(String queue, 
                             	boolean durable, 
                             	boolean exclusive, 
                            	boolean autoDelete,
                                Map<String, Object> arguments) throws IOException;

2) Consumer 01 Code

/**
 * @desc
 * @auth llp
 * @date 2022年08月04日 22:48
 */
public class Consumer01 {
    
    
    /** 普通交换机名称 */
    private static final String NORMAL_EXCHANGE = "normal_exchange";
    /** 普通队列名称 */
    private static final String NORMAL_QUEUE = "normal_queue";
    /** 死信交换机名称 */
    private static final String DEAD_EXCHANGE = "dead_exchange";
    /** 死信队列名称 */
    private static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        Channel channel = RabbitMQUtil.getChannel();
        // 声明交换机,类型为 direct
        channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.DIRECT);

        // 声明普通队列
        Map<String, Object> arguments = new HashMap<>();
        // 过期时间 单位 ms 注:也可以在生产者发送消息指定(比较灵活)
        // arguments.put("x-message-ttl", 5000);
        // 正常的队列要设置死信交换机    注意:key 是固定的
        arguments.put("x-dead-letter-exchange", DEAD_EXCHANGE);
        // 设置死信 RoutingKey
        arguments.put("x-dead-letter-routing-key", "dead");
        channel.queueDeclare(NORMAL_QUEUE, false, false, false, arguments);
        // 声明死信队列
        channel.queueDeclare(DEAD_QUEUE, false, false, false, null);

        // 交换机和队列绑定
        channel.queueBind(NORMAL_QUEUE, NORMAL_EXCHANGE, "normal");
        channel.queueBind(DEAD_QUEUE, DEAD_EXCHANGE, "dead");

        // 接收消息
        System.out.println("Consumer01 等待接收消息...");
        // 消息消费成功的回调  consumerTag:与消费者关联的消费者标签
        DeliverCallback deliverCallback = (consumerTag, message) ->
                System.out.println("Consumer01 接收到的消息为 ==> " + new String(message.getBody(), StandardCharsets.UTF_8));
        // 消息消费失败的回调
        CancelCallback cancelCallback = consumerTag -> System.out.println(consumerTag+" 消息消费中断...");
        channel.basicConsume(NORMAL_QUEUE, true, deliverCallback, cancelCallback);
    }
}

3) Producer code

/**
 * @desc
 * @auth llp
 * @date 2022年08月04日 23:16
 */
public class Producer {
    
    
    /** 普通交换机名称 */
    private static final String NORMAL_EXCHANGE = "normal_exchange";
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        Channel channel = RabbitMQUtil.getChannel();
        // 死信消息 设置 TTL 时间 time to live 单位 ms
        AMQP.BasicProperties properties =
                new AMQP.BasicProperties()
                        .builder().expiration("5000").build();
        for (int i = 1; i <= 10; i++) {
    
    
            String msg = "message" + i;
            channel.basicPublish(NORMAL_EXCHANGE, "normal", properties, msg.getBytes());
        }
    }
}

4) Consumer 02 Code

/**
 * @desc
 * @auth llp
 * @date 2022年08月04日 22:48
 */
public class Consumer02 {
    
    
    /** 死信队列名称 */
    private static final String DEAD_QUEUE = "dead_queue";

    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        Channel channel = RabbitMQUtil.getChannel();
        // 接收消息
        System.out.println("Consumer02 等待接收消息...");
        // 消息消费成功的回调  consumerTag:与消费者关联的消费者标签
        DeliverCallback deliverCallback = (consumerTag, message) ->
                System.out.println("Consumer02 接收到的消息为 ==> " + new String(message.getBody(), StandardCharsets.UTF_8));
        // 消息消费失败的回调
        CancelCallback cancelCallback = consumerTag -> System.out.println(consumerTag+" 消息消费中断...");
        channel.basicConsume(DEAD_QUEUE, true, deliverCallback, cancelCallback);
    }
}

5) Message TTL expired

消费者01Start the code first , declare the corresponding switch and message queue, and then stop 消费者01. Restart 生产者, and send a message with TTL set to the peer.

Test Results:

insert image description here

Start 消费者02consuming messages in the dead letter queue

insert image description here

6) Queue reaches maximum length

消费者01Add the following code:

// 设置普通队列的长度    注意:key 是固定的
arguments.put("x-max-length", 6);

生产者Remove the code for the expiration time

// 第三个参数设置为 null
channel.basicPublish(NORMAL_EXCHANGE, "normal", null, msg.getBytes());

Notice:At this time, the original queue needs to be deleted first, because the parameter setting has changed

insert image description here

run test

消费者01After restarting, you need to turn it off to prevent consumption of messages, resulting in a backlog of messages. Start 生产者it to see if four messages are sent to the dead letter queue.

insert image description here

7) Message rejected

Comment out the maximum length parameter setting of the queue, and the 消费者01modified code is:

// 消息消费成功的回调  consumerTag:与消费者关联的消费者标签
DeliverCallback deliverCallback = (consumerTag, message) -> {
    
    
    String msg = new String(message.getBody(), StandardCharsets.UTF_8);
    if ("message5".equals(msg)){
    
    
        System.out.println("Consumer01 拒绝的消息为 ==> " + msg);
        channel.basicReject(message.getEnvelope().getDeliveryTag(), false);
    }else {
    
    
        System.out.println("Consumer01 接收到的消息为 ==> " + msg);
        channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
    }
};
// 消息消费失败的回调
CancelCallback cancelCallback = consumerTag -> System.out.println(consumerTag+" 消息消费中断...");
// 手动应答 参数2:false
channel.basicConsume(NORMAL_QUEUE, false, deliverCallback, cancelCallback);

run test

insert image description here

Guess you like

Origin blog.csdn.net/weixin_43989102/article/details/126168729