7.rabbitmq死信和死信队列

rabbitmq死信和死信队列

概述

先从概念解释上搞清楚这个定义,死信,顾名思义就是无法被消费的消息,字面意思可以这样理 解,一般来说,producer 将消息投递到 broker 或者直接到 queue 里了,consumer 从 queue 取出消息 进行消费,但某些时候由于特定的原因导致 queue 中的某些消息无法被消费,这样的消息如果没有 后续的处理,就变成了死信,有死信自然就有了死信队列。

应用场景:为了保证订单业务的消息数据不丢失,需要使用到 RabbitMQ 的死信队列机制,当消息 消费发生异常时,将消息投入死信队列中.还有比如说: 用户在商城下单成功并点击去支付后在指定时 间未支付时自动失效

死信的来源

  • 消息 TTL 过期
  • 队列达到最大长度(队列满了,无法再添加数据到 mq 中)
  • 消息被拒绝(basic.rejectbasic.nack)并且 requeue=false.

死信的流程图

7.rabbitmq死信和死信队列_第1张图片

TTL过期

什么是TTL,TTL是Rabbitmq中的一个消息或者队列的属性,表明一条消息或者队列中的所有消息的最大存活时间,单位是毫秒。

如果一条消息设置了 TTL 属性或者进入了设置 TTL 属性的队列,那么这 条消息如果在 TTL 设置的时间内没有被消费,则会成为"死信"。如果同时配置了队列的 TTL 和消息的 TTL,那么较小的那个值将会被使用,有两种方式设置 TTL。

  • 针对每一条消息设置TTL

            AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();
    
  • 队列设置TTL

    Map<String,Object> params = new HashMap<>();
    params.put("x-message-ttl",1000);
    

区别:

如果设置了队列的 TTL 属性,那么一旦消息过期,就会被队列丢弃(如果配置了死信队列被丢到死信队 列中),而第二种方式,消息即使过期,也不一定会被马上丢弃,因为消息是否过期是在即将投递到消费者 之前判定的,如果当前队列有严重的消息积压情况,则已过期的消息也许还能存活较长时间;另外,还需 要注意的一点是,如果不设置 TTL,表示消息永远不会过期,如果将 TTL 设置为 0,则表示除非此时可以 直接投递该消息到消费者,否则该消息将会被丢弃。

生产者代码

/**
 * 死信 TTl过期 生产者
 */
public class TtlProducer {
    public static void main(String[] args) throws IOException {
        Channel channel = RabbitUtil.getChannel();
        channel.exchangeDeclare(ExchangeNames.NORMAL, BuiltinExchangeType.DIRECT);
        //设置消息的TTl时间
        AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("10000").build();

        for (int i = 1; i < 11; i++) {
            String message = "info"+i;
            channel.basicPublish(ExchangeNames.NORMAL,"normal-key",properties,message.getBytes(StandardCharsets.UTF_8));
            System.out.println("生产者发送消息:"+message);
        }
    }
}

消费者1代码,启动之后关闭该消费者,使其接收不到消息

/**
 * 死信 TTL 消费者1
 */
public class TtlCustomer1 {

    public static void main(String[] args) throws IOException {
        Channel channel = RabbitUtil.getChannel();
        //声明死信和普通交换机
        channel.exchangeDeclare(ExchangeNames.NORMAL, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(ExchangeNames.DEAD, BuiltinExchangeType.DIRECT);

        //声明死信队列
        String deadQueue = "dead-queue";
        channel.queueDeclare(deadQueue,false,false,false,null);
        channel.queueBind(deadQueue,ExchangeNames.DEAD,"dead-key");

        //设置参数
        Map<String,Object> params = new HashMap<>();
        //正常队列设置死信交换机,key是固定的
        params.put("x-dead-letter-exchange",ExchangeNames.DEAD);
        //正常队列设置死信的 routing-key, key是固定的
        params.put("x-dead-letter-routing-key","dead-key");
        params.put("x-message-ttl",1000);

        //声明普通的队列
        String normalQueue = "normal-queue";
        channel.queueDeclare(normalQueue,false,false,false,params);
        //绑定队列和交换机
        channel.queueBind(normalQueue,ExchangeNames.NORMAL,"normal-key");
      
        System.out.println("TtlCustomer1 等待接收消息...");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            String receive = new String(message.getBody());
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
            System.out.println("接收绑定键:"+message.getEnvelope().getRoutingKey()+",消息 :"+receive);
        };
        channel.basicConsume(normalQueue,false,deliverCallback,consumerTag -> {});
    }
}

此时,生产者未发送消息,消费者也没有接收到消息

在这里插入图片描述

生产者发送10条消息,此时正常队列有10条消息没有被消费

在这里插入图片描述

时间过去10秒,正常队列里面的消息没有被消费,消息进入了死信队列

在这里插入图片描述

消费者2代码

/**
 * 死信 TTL 消费者2
 */
public class TtlCustomer2 {

    public static void main(String[] args) throws IOException {
        Channel channel = RabbitUtil.getChannel();
        //声明死信和普通交换机
        channel.exchangeDeclare(ExchangeNames.DEAD, BuiltinExchangeType.DIRECT);

        //声明死信队列
        String deadQueue = "dead-queue";
        channel.queueDeclare(deadQueue,false,false,false,null);
        //绑定队列和交换机
        channel.queueBind(deadQueue,ExchangeNames.DEAD,"dead-key");

        System.out.println("TtlCustomer2 等待接收死信队列消息...");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            String receive = new String(message.getBody());
            System.out.println("接收死信队列消息:"+message.getEnvelope().getRoutingKey()+",消息 :"+receive);
        };
        channel.basicConsume(deadQueue,true,deliverCallback,consumerTag -> {});
    }
}

启动消费者2代码,发现死信队列中的消息被消费者2消费

队列达到最大长度

我们可以给一个队列设置最大的长度,当队列中的消息达到了最大长度之后,后面的消息就转入到死信队列中。

Map<String,Object> params = new HashMap<>();
params.put("x-max-length",6);

生产者代码

/**
 * 死信 队列最大长度 生产者
 */
public class MaxLengthProducer {
    public static void main(String[] args) throws IOException {
        Channel channel = RabbitUtil.getChannel();
        channel.exchangeDeclare(ExchangeNames.NORMAL, BuiltinExchangeType.DIRECT);
        for (int i = 1; i < 11; i++) {
            String message = "info"+i;
            channel.basicPublish(ExchangeNames.NORMAL,"normal-key",null,message.getBytes(StandardCharsets.UTF_8));
            System.out.println("生产者发送消息:"+message);
        }
    }
}

消费者1代码

/**
 *
 * 死信 最大队列长度 消费者
 */
public class MaxLengthConsumer1 {

    public static void main(String[] args) throws IOException {
        Channel channel = RabbitUtil.getChannel();
        //声明两个队列,一个普通队列,一个死信队列
        channel.exchangeDeclare(ExchangeNames.DEAD, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(ExchangeNames.NORMAL, BuiltinExchangeType.DIRECT);

        Map<String,Object> params = new HashMap<>();
        params.put("x-dead-letter-exchange",ExchangeNames.DEAD);
        params.put("x-dead-letter-routing-key","dead-key");
        params.put("x-max-length",6);

        String normalQueue = "normal-queue";
        channel.queueDeclare(normalQueue,false,false,false,params);
        channel.queueBind(normalQueue,ExchangeNames.NORMAL,"normal-key");

        String deadQueue = "dead-queue";
        channel.queueDeclare(deadQueue,false,false,false,null);
        channel.queueBind(deadQueue,ExchangeNames.DEAD,"dead-key");

        System.out.println("MaxLengthConsumer1 等待接收消息...");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            String receive = new String(message.getBody());
            channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
            System.out.println("接收绑定键:"+message.getEnvelope().getRoutingKey()+",消息 :"+receive);
        };
        channel.basicConsume(normalQueue,false,deliverCallback,consumerTag -> {});

    }
}

启动消费者1,查看消费未发送时的状态

在这里插入图片描述

关闭消费者1,启动生产者,发送消息

在这里插入图片描述

消费者2代码

/**
 * 死信 最大队列长度 消费者
 */
public class MaxLengthConsumer2 {

    public static void main(String[] args) throws IOException {
        Channel channel = RabbitUtil.getChannel();
        //声明死信交换机
        channel.exchangeDeclare(ExchangeNames.DEAD, BuiltinExchangeType.DIRECT);

        //声明死信队列
        String deadQueue = "dead-queue";
        channel.queueDeclare(deadQueue,false,false,false,null);
        //绑定队列和交换机
        channel.queueBind(deadQueue,ExchangeNames.DEAD,"dead-key");


        System.out.println("MaxLengthConsumer2 等待接收死信队列消息...");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            String receive = new String(message.getBody());
            System.out.println("接收死信队列消息:"+message.getEnvelope().getRoutingKey()+",消息 :"+receive);
        };
        channel.basicConsume(deadQueue,true,deliverCallback,consumerTag -> {});
    }
}

启动消费者2,消费掉死信队列中的消息之后,如下图所示

在这里插入图片描述

消息被拒

普通队列拒绝接收消息之后,将消息转入死信队列,拒绝接收消息的方法如下所示

  • 第二个参数requeue:设置为false,代表拒接重新入队,该队列如果配置了死信队列那么将进入到死信队列中
channel.basicReject(message.getEnvelope().getDeliveryTag(),false);

生产者代码

/**
 * 消息被拒 生产者
 */
public class RejectProducer {
    public static void main(String[] args) throws IOException {
        Channel channel = RabbitUtil.getChannel();
        channel.exchangeDeclare(ExchangeNames.NORMAL, BuiltinExchangeType.DIRECT);
        for (int i = 1; i < 11; i++) {
            String message = "info"+i;
            channel.basicPublish(ExchangeNames.NORMAL,"normal-key",null,message.getBytes(StandardCharsets.UTF_8));
            System.out.println("生产者发送消息:"+message);
        }
    }
}

消费者1代码

/**
 *
 * 死信 消息被拒 消费者1
 */
public class RejectConsumer1 {

    public static void main(String[] args) throws IOException {
        Channel channel = RabbitUtil.getChannel();
        //声明两个队列,一个普通队列,一个死信队列
        channel.exchangeDeclare(ExchangeNames.DEAD, BuiltinExchangeType.DIRECT);
        channel.exchangeDeclare(ExchangeNames.NORMAL, BuiltinExchangeType.DIRECT);

        Map<String,Object> params = new HashMap<>();
        params.put("x-dead-letter-exchange",ExchangeNames.DEAD);
        params.put("x-dead-letter-routing-key","dead-key");

        String normalQueue = "normal-queue";
        channel.queueDeclare(normalQueue,false,false,false,params);
        channel.queueBind(normalQueue,ExchangeNames.NORMAL,"normal-key");

        String deadQueue = "dead-queue";
        channel.queueDeclare(deadQueue,false,false,false,null);
        channel.queueBind(deadQueue,ExchangeNames.DEAD,"dead-key");

        System.out.println("RejectConsumer1 等待接收消息...");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            String receive = new String(message.getBody());
            if (receive.equals("info5")){
                System.out.println("RejectConsumer1接收到消息:"+receive+",但是拒接接收了");
                //requeue 设置为false,代表拒接重新入队 该队列如果配置了死信队列那么将进入到死信队列中
                channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
            }else {
                System.out.println("RejectConsumer1接收到消息:"+receive);
                channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
            }
        };
        channel.basicConsume(normalQueue,false,deliverCallback,consumerTag -> {});

    }
}

启动消费者1,队列信息如下所示

在这里插入图片描述

启动生产者,发送消息,info5被拒绝接收进入死信

在这里插入图片描述

消费者2代码

/**
 *
 * 死信 消息被拒 死信 消费者
 */
public class RejectConsumer2 {

    public static void main(String[] args) throws IOException {
        Channel channel = RabbitUtil.getChannel();
        //声明死信交换机
        channel.exchangeDeclare(ExchangeNames.DEAD, BuiltinExchangeType.DIRECT);

        //声明死信队列
        String deadQueue = "dead-queue";
        channel.queueDeclare(deadQueue,false,false,false,null);
        //绑定队列和交换机
        channel.queueBind(deadQueue,ExchangeNames.DEAD,"dead-key");


        System.out.println("RejectConsumer2 等待接收死信队列消息...");

        DeliverCallback deliverCallback = (consumerTag, message) -> {
            String receive = new String(message.getBody());
            System.out.println("接收死信队列消息:"+message.getEnvelope().getRoutingKey()+",消息 :"+receive);
        };
        channel.basicConsume(deadQueue,true,deliverCallback,consumerTag -> {});
    }
}

启动消费者2消费死信队列中的消息

7.rabbitmq死信和死信队列_第2张图片

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