RabbitMQ(五)死信队列与延时队列

一、介绍死信队列、延时队列

1、死信队列

DLX,全称为 Dead-Letter-Exchange,可以称为死信交换机,也有人称之为死信邮箱,当消息在一个正常队列中变成死信(dead message)之后,它能被重新发送到另一个交换机中,这个交换机就是DLX,绑定DLX的队列就称之为死信队列

消息变成死信,可能由于以下原因:

  • 消息被拒绝
  • 消息过期
  • 队列达到最大长度

DLX也是一个正常的交换机,和一般的交换机没有区别,它能在任何队列上被指定。实际上就是设置某一个队列的属性,当这个队列中存在死信时,rabbitMQ就会自动的将这个消息重新发布到设置的DLX上去,进而被路由到另一个队列,即死信队列。

想要使用死信队列,只需要在定义队列的时候设置队列参数【x-dead-letter-exchange 指定交换机】即可

2、延时队列

延时队列存储的对象是对应的延时消息。所谓 “延时消息” 是指当消息被发送以后,并不想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。

在RabbitMQ中并没有支持延时队列的功能,延时队列可以通过 【过期时间 + 死信队列】 来实现

注意:创建的延时队列千万不要有消费者,一旦有消费者进行这个队列的消费,那么就不会是特定延时了,目的就是要这个队列里的消息TTL过时

具体流程如下:

 

二、延时队列的实现

结构大致如下 

 

 

1、基本的springboot配置依赖

资源文件(rabbitConfig.properties)

从中获取交换机、队列、路由key信息名


#正常的订单交换机(路由模式)
learn_annotation_delay_OrderTopicExchange=learn_annotation_delay_OrderTopicExchange
#订单延时队列(实现超时关闭订单功能)
learn_annotation_delay_OrderTopictQueue_DelayCancelQueue=learn_annotation_delay_OrderTopictQueue_DelayCancelQueue
#进入延时队列的路由key
learn_annotation_delay_OrderTopictQueue_DelayCancelQueue_key=delay_OrderTopictQueue_DelayCancelQueue_key


#死信交换机
learn_annotation_delay_DirectExchange_DLX=learn_annotation_delay_DirectExchange_DLX
#死信队列
learn_annotation_delay_DirectQueue_DLX_Queue=learn_annotation_delay_DirectQueue_DLX_Queue
#死信队列路由key
learn_annotation_delay_DirectQueue_DLX_Queue_key=delay_DLX_key

2、绑定延时队列与交换机

目的:定义延时队列并设置对应的死性交换机与路由key;绑定死性队列与交换机

package com.marvin.demo.common.rabbitmq.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

/**
 * 延迟队列配置【创建所需交换机、队列,并且绑定】
 */
@Configuration
@PropertySource("classpath:rabbitConfig.properties")
public class RabbitMqDelayResourceConfig {

    //读取资源文件信息
    @Autowired
    private Environment env;


    //定义订单交换机【就是一个普通的交换机】
    @Bean("orderExchange")
    public Exchange orderExchange() {
        return ExchangeBuilder.topicExchange(env.getProperty("learn_annotation_delay_OrderTopicExchange")).build();
    }

    //定义延时队列【订单超时自动关闭队列】
    /**
     * 定义延时队列 And 设置死信消息的转发配置【方法一】
     *
     * 1、创建一个延时队列【该队列不能有消费者,队列消息才会是死信消息】
     * 2、设置该队列产生死信时发送到指定死信交换机
     * 3、死信队列的routing key
     *
     * @return
     */
    @Bean("delayOrderCancelQueue")
    public Queue delayOrderCancelQueue() {
        return QueueBuilder.durable(env.getProperty("learn_annotation_delay_OrderTopictQueue_DelayCancelQueue"))
                // x-dead-letter-exchange 声明了队列里的死信消息转发到的DLX名称,
                .withArgument("x-dead-letter-exchange", env.getProperty("learn_annotation_delay_DirectExchange_DLX"))
                // x-dead-letter-routing-key 声明了这些死信消息在转发时携带的 routing-key 名称。
                .withArgument("x-dead-letter-routing-key", env.getProperty("learn_annotation_delay_DirectQueue_DLX_Queue_key")).build();
    }
//    /**
//     * 定义延时队列 And 设置死信消息的转发配置【方法二】
//     */
//    @Bean("delayQueue")
//    public Queue delayQueue() {
//        //设置死信交换机和路由key
//        Map<String, Object> params = new HashMap<>();
//        // x-dead-letter-exchange 声明了队列里的死信转发到的DLX名称,
//        params.put("x-dead-letter-exchange", env.getProperty("learn_annotation_delay_DirectExchange_DLX"));
//        // x-dead-letter-routing-key 声明了这些死信在转发时携带的 routing-key 名称。
//        params.put("x-dead-letter-routing-key", env.getProperty("learn_annotation_delay_DirectQueue_DLX_Queue_key"));
//        return new Queue(env.getProperty("learn_annotation_delay_OrderTopictQueue_DelayCancelQueue"), true, false, false, params);
//    }

    //绑定延时队列与订单交换机
    @Bean("delayOrderCancelBinding")
    public Binding delayOrderCancelBinding(@Qualifier("orderExchange")Exchange orderExchange, @Qualifier("delayOrderCancelQueue")Queue delayOrderCancelQueue) {
        return BindingBuilder.bind(delayOrderCancelQueue).to(orderExchange).with(env.getProperty("learn_annotation_delay_OrderTopictQueue_DelayCancelQueue_key")).noargs();
    }



    //定义死信交换机
    @Bean("dlxExchange")
    public Exchange dlxExchange() {
        return ExchangeBuilder.directExchange(env.getProperty("learn_annotation_delay_DirectExchange_DLX")).build();
    }

    //定义死信队列
    @Bean("dlxQueue")
    public Queue dlxQueue() {
        return QueueBuilder.durable(env.getProperty("learn_annotation_delay_DirectQueue_DLX_Queue")).build();
    }

    //绑定死信队列与交换机
    @Bean("dlxBinding")
    public Binding dlxBinding(@Qualifier("dlxExchange")Exchange dlxExchange, @Qualifier("dlxQueue")Queue dlxQueue) {
        return BindingBuilder.bind(dlxQueue).to(dlxExchange).with(env.getProperty("learn_annotation_delay_DirectQueue_DLX_Queue_key")).noargs();
    }
}

3、发送延时队列消息

 目的:发送有实效的消息到延时队列,从死信队列接收消息;从而实现消息的延时消费

package com.marvin.demo.service.producer;

import com.marvin.demo.entity.UserBean;
import com.marvin.demo.model.UserRequestModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.UUID;

@Slf4j
@Component
@PropertySource("classpath:rabbitConfig.properties")
public class ProducerService {

    @Autowired
    private Environment env;

    @Autowired
    private RabbitTemplate rabbitTemplate;


    /**
     * 发送消息到延时队列【订单延时关闭场景】
     */
    public void sendMsgToDelayQueue() {

        //订单交换机
        String orderExchange = env.getProperty("learn_annotation_delay_OrderTopicExchange");
        //延时队列路由key(消息路由到订单延时关闭队列)
        String routingKey = env.getProperty("learn_annotation_delay_OrderTopictQueue_DelayCancelQueue_key");

        UserBean userBean = new UserBean(1, "1_a", "aa");
        UserRequestModel userRequest = new UserRequestModel();
        userRequest.setType("junitTest");
        userRequest.setStatus("success");
        userRequest.setDesc(String.format("method【sendMsgToDelayQueue()】routingKey【%s】", routingKey));
        userRequest.setContent(userBean);

        //设置消息的唯一标识(根据情况可加可不加)
        String uuid = UUID.randomUUID().toString();
        CorrelationData correlationData = new CorrelationData(uuid);

        /**
         * 参数事例
         * rabbitTemplate.convertAndSend(String exchange, String routingKey, final Object message, final MessagePostProcessor messagePostProcessor, @Nullable CorrelationData correlationData)
         *
         * @param exchange 交换机
         * @param routingKey 路由key
         * @param message 消息
         * @param messagePostProcessor 消息处理器
         * @param correlationData 消息的唯一标识(根据情况可加可不加)
         */
        rabbitTemplate.convertAndSend(orderExchange, routingKey, userRequest, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                //设置消息过期时间 10 秒
                message.getMessageProperties().setExpiration("10000");
                return message;
            }
        }, correlationData);
        log.info("Send method【sendMsgToDelayQueue()】routingKey【{}】", routingKey);
        log.info("向延时队列发送时间:【{}】", new Date());

    }
}

4、死信队列接收消息进行消费

package com.marvin.demo.service.consumer;

import com.marvin.demo.entity.UserBean;
import com.marvin.demo.model.UserRequestModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.context.annotation.PropertySource;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import java.util.Date;

/**
 * 死信队列消息监听服务
 */
@Slf4j
@Component
@PropertySource("classpath:rabbitConfig.properties")
@RabbitListener(queues = "learn_annotation_delay_DirectQueue_DLX_Queue")
public class ConsumerDLXService {

    @RabbitHandler
    public void processDLX(@Payload UserRequestModel userRequestModel){
        log.info("Enter ConsumerDLXService --> processDLX()~~~~~~~~~~~~~~~~~~~");
        log.info("死信队列消费时间:【{}】", new Date());
        System.out.println("ConsumerDLXService queue1 msg:" + userRequestModel);
        System.out.println("ConsumerDLXService Object1 UserRequestModel:" + userRequestModel.toString());
        //获取真正的数据对象
        UserBean userBean = userRequestModel.getContent();
        System.out.println("ConsumerDLXService Object1 UserBean:" + (ObjectUtils.isEmpty(userBean) ? null : userBean.toString()));
    }




}

结果验证

打印日志如下,可以看到从发送到延时队列,到死信队列消费,经过的耗时刚好是我们设定的10秒(真实场景下需要考虑消费者的执行效率耗时) 

2022-06-11 00:00:48.065  INFO 11600 --- [nio-8004-exec-1] c.m.d.service.producer.ProducerService   : Send method【sendMsgToDelayQueue()】routingKey【delay_OrderTopictQueue_DelayCancelQueue_key】
2022-06-11 00:00:48.076  INFO 11600 --- [ 127.0.0.1:5672] c.m.d.common.rabbitmq.MessageCallBack    : 【成功】【confirmCallback】Client消息发送到Exchange ==================== correlationData【CorrelationData [id=09745a87-5ada-4876-aa87-f59b654b19b6]】, cause: 【null】
2022-06-11 00:00:48.082  INFO 11600 --- [nio-8004-exec-1] c.m.d.service.producer.ProducerService   : 向延时队列发送时间:【Sat Jun 11 00:00:48 CST 2022】
2022-06-11 00:00:58.243  INFO 11600 --- [ntContainer#0-1] c.m.d.s.consumer.ConsumerDLXService      : Enter ConsumerDLXService --> processDLX()~~~~~~~~~~~~~~~~~~~
2022-06-11 00:00:58.243  INFO 11600 --- [ntContainer#0-1] c.m.d.s.consumer.ConsumerDLXService      : 死信队列消费时间:【Sat Jun 11 00:00:58 CST 2022】
ConsumerDLXService queue1 msg:UserRequestModel{type='junitTest', status='success', desc='method【sendMsgToDelayQueue()】routingKey【delay_OrderTopictQueue_DelayCancelQueue_key】', content=UserBean{id=1, username='1_a', pwd='aa'}}
ConsumerDLXService Object1 UserRequestModel:UserRequestModel{type='junitTest', status='success', desc='method【sendMsgToDelayQueue()】routingKey【delay_OrderTopictQueue_DelayCancelQueue_key】', content=UserBean{id=1, username='1_a', pwd='aa'}}
ConsumerDLXService Object1 UserBean:UserBean{id=1, username='1_a', pwd='aa'}

从rabbitMQ服务可以看到,消息是先进入延时队列中的 ,该队列消息超时后,会转发到设定的死信队列中

 

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值