RabbitMQ高级特性——死信队列DLX以及代码测试

大伙可以到我的RabbitMQ专栏获取更多信息

demo示例这里拿

概述

死信队列,缩写DLX(dead letter exchange 死信交换机),当消息称为dead message之后,会被重新发送到另一个交换机,这个交换机就是DLX

当msg在Queue中过期之后,并没有直接被丢弃,而是会被当前的Queue转发给死信交换机DLX,DLX再重新发送给另外一个Queue供其他消费者消费掉

为什么缩写不是DLQ或者是死信交换机呢?

因为在其他MQ产品中是没有交换机这个概念的,所以在RabbitMQ中也叫死信队列了,实际是死信交换机

消息在什么情况下会称为死信

  • 过期的消息:队列中的消息到达了过期时间还没有被消费,就会变成死信
  • 队列中消息的长度到达限制:超过某个队列的最大存储消息个数之后的,其他被exchange分发到该队列的消息直接称为死信队列
  • 消费端拒接消息:当消费者端手动确认模式下拒绝了某条消息,并且设置了requeue=false之后,这条消息并不会被放回原队列,而是变为死信

队列绑定死信交换机

给队列设置参数:x-dead-letter-exchangex-dead-letter-routing-key

测试示例

发送代码示例

以下不同情况的测试自行修改发送消息代码

    /*
     * 功能描述: <br>
     * 〈测试死信队列〉
     * 1、过期的消息:队列中的消息到达了过期时间还没有被消费,就会变成死信
     * 2、队列中消息的长度到达限制:超过某个队列的最大存储消息个数之后的,其他被exchange分发到该队列的消息直接称为死信队列
     * 3、消费端拒接消息:当消费者端手动确认模式下拒绝了某条消息,并且设置了requeue=false之后,这条消息并不会被放回原队列,而是变为死信
     * @Param:
     * @Return:
     * @Author: LeoLee
     * @Date: 2020/11/8 14:50
     */
    @Test
    public void testDLX() {

        //这些消息将会被发到正常队列中
        for (int i = 0; i < 10; i++) {
            //该条消息测试队列过期
            rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME, "ttl.abc", "test msg dlx");

        }
    }

创建死信交换机和死信队列并将两者绑定

死信交换机和死信队列的创建没有什么特别的地方

当死信交换机和死信队列创建并绑定完成后,要设置正常队列对应的死信交换机以及被该正常队列丢弃的死信的routing key。该routing key决定了死信交换机接收到死信之后分发给哪个一个死信队列(死信队列可能不止一个)

package com.leolee.rabbitmq.config;

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

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

/**
 * @ClassName RabbitMQConfig
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/11/7
 * @Version V1.0
 **/
@Configuration
public class RabbitMQConfig {

    public static final String EXCHANGE_NAME = "boot_topic_exchange";

    public static final String DEAD_LETTER_EXCHANGE_NAME = "dlx_exchange";


    public static final String QUEUE_NAME = "boot_queue";

    public static final String QUEUE_TTL_NAME = "ttl_queue";

    public static final String DEAD_LETTER_QUEUE_NAME = "dlx_queue";

    //交换机
    @Bean("bootExchange")
    public Exchange bootExchange() {

        return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
    }


    //死信交换机
    @Bean("deadLetterExchange")
    public Exchange deadLetterExchange() {

        return ExchangeBuilder.topicExchange(DEAD_LETTER_EXCHANGE_NAME).durable(true).build();
    }


    //队列
    @Bean("bootQueue")
    public Queue bootQueue() {

        return QueueBuilder.durable(QUEUE_NAME).build();
    }


    //设置TTL的队列
    @Bean("queueWithTTL")
    public Queue queueWithTTL() {

        Map<String, Object> argumentsMap = new HashMap<>();
        //设置队列超时时间
        argumentsMap.put("x-message-ttl", 50000);
        //设置该队列的长度
        argumentsMap.put("x-max-length", 10);
        //设置该队列的死信队列
        argumentsMap.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE_NAME);
        //设置死信的routing key,能保证在死信交换机中能匹配到死信队列即可
        argumentsMap.put("x-dead-letter-routing-key", "dlx.test");
        return QueueBuilder.durable(QUEUE_TTL_NAME).withArguments(argumentsMap).build();
    }


    //对应死信交换机的队列
    @Bean("deadLetterQueue")
    public Queue deadLetterQueue() {

        return QueueBuilder.durable(DEAD_LETTER_QUEUE_NAME).build();
    }


    //交换机队列绑定关系
    @Bean
    public Binding bindQueueExchange(@Qualifier("bootQueue") Queue queue, @Qualifier("bootExchange") Exchange exchange) {

        return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs();//noargs,不传递参数
    }


    //绑定TTL队列到交换机
    @Bean
    public Binding bingQueueTTLExchange(@Qualifier("queueWithTTL") Queue queue, @Qualifier("bootExchange") Exchange exchange) {

        return BindingBuilder.bind(queue).to(exchange).with("ttl.#").noargs();
    }


    //绑定死信队列到死信交换机
    @Bean
    public Binding bingDLQToDLX(@Qualifier("deadLetterQueue") Queue queue, @Qualifier("deadLetterExchange") Exchange exchange) {

        return BindingBuilder.bind(queue).to(exchange).with("dlx.#").noargs();
    }
}

过期时间死信

运行代码发送了10条消息,正常队列ttl_queue接收到消息:

过期之后:

  1. ttl_queue将过期的消息发送给了死信交换机dlx_exchange
  2. 死信交换机dlx_exchange开始根据ttl_queue设置的死信routing key[dlx.test]匹配死信队列
  3. 匹配到死信队列dlx_queue,将10条过期消息发送给dlx_queue等待被消费

队列过长消息死信

队列ttl_queue设置了[x-max-length:10],最大容纳10条消息在队列中等待消费,之后被分发给ttl_queue的消息直接变成死信队列

发送15条消息到ttl_queue:

由于超过10跳的长度,剩余的5条直接被丢弃给死信队列

剩下的10条在队列ttl_queue中等待消费或者是过期,由于我没有消费者去消费,剩下10条消息在过期时候也被传递给了死信队列,加上上一次测试的10条死信,一共是25条死信:

消费者拒绝死信

消费者代码复用之前讲解 Consumer Ack的代码:

设置channel.basicNack(deliveryTag, multiple, requeue)中requeue为false

划重点:requeue: true该条消息重新返回MQ queue,MQ broker将会重新发送该条消息,false的话会被转发到该队列独赢的死信队列,如果没有对应的死信队列则消息被丢弃

package com.leolee.rabbitmq.MsgListener;

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

import java.io.IOException;

/**
 * @ClassName AckListener
 * @Description: Consumer Ack
 * 1.设置手动确认签收:acknowledge-mode: manual, retry.enabled: true #是否支持重试
 * 2.实现ChannelAwareMessageListener接口,ChannelAwareMessageListener是MessageListener的子接口
 * 3.如果消息接收并处理完成,调用channel.basicAck()向MQ确认签收
 * 4.如果消息接收但是业务处理失败,调用channel.basicNack()拒收,要求MQ重新发送
 * @Author LeoLee
 * @Date 2020/11/7
 * @Version V1.0
 **/
@Component
public class DLXListener implements ChannelAwareMessageListener {


    @RabbitListener(queues = "ttl_queue")
    @Override
    public void onMessage(Message message, Channel channel) throws Exception {

        Thread.sleep(1000);
        boolean tag = new String(message.getBody()).contains("true");
        System.out.println("接收到msg:" + new String(message.getBody()));
        //获取mes deliveryTag
        long deliveryTag = message.getMessageProperties().getDeliveryTag();

        try {
            if (tag) {
                System.out.println("业务处理成功");
                //手动签收
                /*
                 * deliveryTag:the tag from the received {@link com.rabbitmq.client.AMQP.Basic.GetOk} or {@link com.rabbitmq.client.AMQP.Basic.Deliver}
                 * multiple: ture确认本条消息以及之前没有确认的消息,false仅确认本条消息
                 */
                channel.basicAck(deliveryTag, false);
            } else {
                //模拟业务处理失败抛出异常
                System.out.println("业务处理失败");
                throw new IOException("业务处理失败");
            }
        } catch (IOException e) {
            e.printStackTrace();
            /*
             * deliveryTag:the tag from the received {@link com.rabbitmq.client.AMQP.Basic.GetOk} or {@link com.rabbitmq.client.AMQP.Basic.Deliver}
             * multiple: ture确认本条消息以及之前没有确认的消息,false仅确认本条消息
             * requeue: true该条消息重新返回MQ queue,MQ broker将会重新发送该条消息,false的话会被转发到该队列独赢的死信队列,如果没有对应的死信队列则消息被丢弃
             */
            channel.basicNack(deliveryTag, false, false);
            //也可以使用channel.basicReject(deliveryTag, requeue),它只能拒收单条消息
            //channel.basicReject(deliveryTag, true);
        }

    }
}

发送两条内容信息没有包含字符串 "true" 的消息到ttl_queue:

消费者拒收了这两条消息

被拒收的消息被拒收回到ttl_queue之后,ttl_queue根据拒收参数requeue=false判断:这两条消息为死信,转发给死信交换机,最终被死信队列dlx_queue接收,加上之前测试的25条死信,一共27条:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值