RabbitMQ——延迟队列

1. 延迟队列

1.1 概念

延迟队列内部是有序的,其中的元素是希望在指定时间到了之后取出或者处理,总之就是用来存放需要在指定时间被处理的元素的队列。

1.2 使用场景

用来在一定时间间隔内对相应的事件做出提醒或者操作。

1.3 整合SpringBoot

正常新建SpringBoot项目即可。SpringBoot版本为2.5.6,如果版本过高与Swagger2版本相差过大就会抛出异常。
添加依赖:

<dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-amqpartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
            <version>2.5.6version>
        dependency>

        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.78version>
        dependency>

        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>1.18.20version>
        dependency>

        <dependency>
            <groupId>io.springfoxgroupId>
            <artifactId>springfox-swagger2artifactId>
            <version>2.9.2version>
        dependency>

        <dependency>
            <groupId>io.springfoxgroupId>
            <artifactId>springfox-swagger-uiartifactId>
            <version>2.9.2version>
        dependency>

        <dependency>
            <groupId>org.springframework.amqpgroupId>
            <artifactId>spring-rabbit-testartifactId>
            <scope>testscope>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>

新增Swagger配置类:

/**
 * @Description swagger配置类
 * @date 2022/3/10 9:39
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket webApiConfig(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .build();
    }

    private ApiInfo webApiInfo(){
        return new ApiInfoBuilder()
                .title("RabbitMQ接口文档")
                .description("RabbitMQ文档接口定义")
                .version("1.0")
                .contact(new Contact("RabbitMQ","https://www.baidu.com"
                        ,"[email protected]"))
                .build();
    }
}

配置文件:

spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

1.4 延迟队列实战

1.4.1 图示

RabbitMQ——延迟队列_第1张图片

  1. P:生产者;
  2. X:普通交换机;
  3. Y:延迟交换机;
  4. QA:延迟10s普通队列;
  5. QB:延迟40s普通队列;
  6. QD:死信队列;
  7. C:消费者。

1.4.2 队列配置

主要是基于SpringBoot方式将队列交换机RotingKey等绑定起来。

/**
 * @Description ttl 队列配置文件
 * @date 2022/3/10 9:53
 */
@Configuration
public class TtlQueueConfig {

    // 普通交换机
    private static final String X_EXCHANGE = "X";

    // 死信交换机
    private static final String DEAD_LETTER_Y_EXCHANGE = "Y";

    // 普通队列
    private static final String QUEUE_A = "QA";
    private static final String QUEUE_B = "QB";

    // 死信队列
    private static final String DEAD_LETTER_QUEUE_D = "QD";

    // 设置普通交换机
    @Bean("xExchange")
    public DirectExchange xExchange(){
        return new DirectExchange(X_EXCHANGE);
    }

    // 设置死信交换机
    @Bean("yExchange")
    public DirectExchange yExchange(){
        return new DirectExchange(DEAD_LETTER_Y_EXCHANGE);
    }

    // 设置普通队列
    @Bean("queueA")
    public Queue aQueue(){
        Map<String ,Object> argument = new HashMap<>();
        // 设置死信交换机
        argument.put("x-dead-letter-exchange",DEAD_LETTER_Y_EXCHANGE);
        // 设置死信RoutingKey
        argument.put("x-dead-letter-routing-key","YD");
        // 设置过期时间
        argument.put("x-message-ttl",10000);
        return QueueBuilder.durable(QUEUE_A).withArguments(argument).build();
    }

    @Bean("queueB")
    public Queue bQueue(){
        Map<String ,Object> argument = new HashMap<>();
        // 设置死信交换机
        argument.put("x-dead-letter-exchange",DEAD_LETTER_Y_EXCHANGE);
        // 设置死信RoutingKey
        argument.put("x-dead-letter-routing-key","YD");
        // 设置过期时间
        argument.put("x-message-ttl",40000);
        return QueueBuilder.durable(QUEUE_B).withArguments(argument).build();
    }


    // 设置死信队列
    @Bean("queueD")
    public Queue dQueue(){
        return QueueBuilder.durable(DEAD_LETTER_QUEUE_D).build();
    }

    /**
     * 队列A绑定交换机X
     * @return
     */
    @Bean
    public Binding queueABindingX(@Qualifier("queueA") Queue queueA,
                                  @Qualifier("xExchange") DirectExchange xExchange) {
        return BindingBuilder.bind(queueA).to(xExchange).with("XA");
    }

    /**
     * 队列B绑定交换机X
     * @return
     */
    @Bean
    public Binding queueBBindingX(@Qualifier("queueB") Queue queueB,
                                  @Qualifier("xExchange") DirectExchange xExchange) {
        return BindingBuilder.bind(queueB).to(xExchange).with("XB");
    }

    /**
     * 队列D绑定交换机Y
     * @return
     */
    @Bean
    public Binding queueDBindingY(@Qualifier("queueD") Queue queueD,
                                  @Qualifier("yExchange") DirectExchange xExchange) {
        return BindingBuilder.bind(queueD).to(xExchange).with("YD");
    }

}

1.4.3 控制器模拟生产者发送消息

/**
 * @Description 接收消息控制器
 * @date 2022/3/10 10:49
 */
@Slf4j
@RestController
@RequestMapping("/ttl")
public class SendMessageController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/sendMsg/{message}")
    public void sendMsg(@PathVariable String message){
        log.info("当前时间:{},发送一条信息给TTL队列:{}",new Date().toString(),message);
        // 执行发送消息到X交换机RoutingKey为XA,XB
        rabbitTemplate.convertAndSend("X","XA","消息来自TTL为10s的队列" + message);
        rabbitTemplate.convertAndSend("X","XB","消息来自TTL为40s的队列" + message);
    }

}

1.4.4 消费者

/**
 * @Description 接收死信队列消费者
 * @date 2022/3/10 15:29
 */
@Slf4j
@Component
public class DeadLetterConsumer {


    // 声明队列监听器,监听死信队列QD
    @RabbitListener(queues = "QD")
    public void receiveD(Message message, Channel channel){
        String msg = new String(message.getBody());
        log.info("当前时间:{},收到来自死信队列消息:{}",new Date().toString(),msg);
    }

}

1.4.5 测试和结果

测试方式:访问http://localhost:8088/ttl/sendMsg/测试,访问了这个界面之后控制器会发送一条消息到正常的交换机中,等待10/40秒后会进入死信队列由消费者接收。
在这里插入图片描述

1.5 优化延迟队列

上方设计中,每新增一个时间需求就需要新增一个队列,这样会占有较多的资源,并且不便于管理。

1.5.1 图示

RabbitMQ——延迟队列_第2张图片
优化思路:在原先项目的基础上新增一个先不对时间进行限制队列QC,QC的时间限制在生产者发送消息的时候指定。

1.5.2 队列配置

在原先的配置基础上进行修改,新增队列QC,并且绑定它和交换机X,Y的关系。

声明队列:

private static final String QUEUE_C = "QC";

配置队列:

  public Queue cQueue(){
        Map<String ,Object> argument = new HashMap<>();
        // 设置死信交换机
        argument.put("x-dead-letter-exchange",DEAD_LETTER_Y_EXCHANGE);
        // 设置死信RoutingKey
        argument.put("x-dead-letter-routing-key","YD");
        return QueueBuilder.durable(QUEUE_C).withArguments(argument).build();
    }

绑定交换机:

/**
     * 队列C绑定交换机X
     * @return
     */
    @Bean
    public Binding queueCBindingX(@Qualifier("queueC") Queue queueC,
                                  @Qualifier("xExchange") DirectExchange xExchange) {
        return BindingBuilder.bind(queueC).to(xExchange).with("XC");
    }

1.5.3 新增控制器方法

/**
     * 发送过期时间和消息
     * @param time 过期时间,单位毫秒
     * @param message 消息
     */
    @GetMapping("/sendTimeAndMsg/{time}/{message}")
    public void sendTimeAndMsg(@PathVariable String time,@PathVariable String message){
        log.info("当前时间:{},发送一条延迟{}毫秒的信息给TTL队列:{}",new Date().toString(),time,message);
        rabbitTemplate.convertAndSend("X",
                "XC",
                "来自交换机X延迟为" + time +"毫秒的消息:" + message,
                msg -> {
                    //  设置时间
                    msg.getMessageProperties().setExpiration(time);
                    return msg;
                }) ;

    }

1.5.4 测试

依次访问:
http://localhost:8088/ttl/sendTimeAndMsg/20000/20sMessage,
http://localhost:8088/ttl/sendTimeAndMsg/2000/2sMessage

按照上方的延迟时间,如果相隔的时间不超过20秒,应该是先收到延迟为2秒的消息。在这里插入图片描述但是显然并不是按照两秒的时间来进行接收的。这就引出了一个新的问题:RabbitMQ只会检测第一个消息是否过期,只要第一条没有到期后面的也不会优先得到执行。

1.6 基于插件延迟队列

1.6.1 优势

在使用插件之前:RabbitMQ——延迟队列_第3张图片
使用了插件之后:
在这里插入图片描述
也就是说消息停留在生产者将消息发送给交换机之后,等停留的时间过了之后,交换机才会将消息发给队列,并且只需要一个队列和一个交换机即可完成。

1.6.2 安装插件

下载插件:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases

下载之后的ez文件复制到RabbitMQ安装目录下的plugins文件夹下,然后使用cmd进入到RabbitMQ安装目录下的sbin文件夹下运行:

rabbitmq-plugins enable rabbitmq_delayed_message_exchange

如图:
RabbitMQ——延迟队列_第4张图片
然后进入到web端,随便新增一个交换机,可以看到下拉框中多了一个x-delayed-message选项:
RabbitMQ——延迟队列_第5张图片
这里就表示安装完成了。

1.6.3 实战

1.6.3.1 图示

直接将消息发送给交换机,交换机等待相应时间之后再发给队列。
RabbitMQ——延迟队列_第6张图片

1.6.3.2 配置类
/**
 * @Description 延迟队列配置类
 * @date 2022/3/10 19:18
 */
@Configuration
public class DelayedQueueConf {

    // 交换机
    private static final String DELAYED_EXCHANGE = "delayed.exchange";

    // 队列
    private static final String DELAYED_QUEUE = "delayed.queue";

    // rotingKey
    private static final String DELAYED_ROUTING_KEY = "delayed.routingKey";

    /**
     * 注入队列
     * @return
     */
    @Bean("delayQueue")
    public Queue delayedQueue(){
        return new Queue(DELAYED_QUEUE);
    }


    /**
     * 注入交换机
     * @return
     */
    @Bean
    public CustomExchange delayedExchange(){
        Map<String ,Object> args = new HashMap<>();
        // 延迟类型
        args.put("x-delayed-type","direct");
        /**
         *      1. 名称
         *      2. 类型
         *      3. 是否持久化
         *      4. 是否自动删除
         *      5. 其他参数
         */
        return new CustomExchange(DELAYED_EXCHANGE,"x-delayed-message",
                false,false,args);
    }

    /**
     * 绑定延迟队列和交换机,routingKey
     * @param queue 队列
     * @param customExchange 交换机
     * @return
     */
    @Bean
    public Binding delayQueueBindingDelayExchange(@Qualifier("delayQueue") Queue queue,
                                                  @Qualifier("delayedExchange") CustomExchange customExchange){
        return BindingBuilder.bind(queue).to(customExchange).with(DELAYED_ROUTING_KEY).noargs();
    }

}

1.6.3.4 生产者控制器
    /**
     * 基于插件的延迟队列
     * @param time 延迟时间 , 单位毫秒
     * @param message 消息内容
     */
    @GetMapping("/sendTimeAndMsgByPlugins/{time}/{message}")
    public void sendTimeAndMsgByPlugins(@PathVariable Integer time,@PathVariable String message){
        log.info("当前时间:{},发送一条延迟{}毫秒的信息给延迟队列:{}",new Date().toString(),time,message);
        rabbitTemplate.convertAndSend("delayed.exchange",
                "delayed.routingKey",message,
                msg -> {
                    msg.getMessageProperties().setDelay(time);
                    return msg;
        });
    }
1.6.3.5 消费者监听
/**
 * @Description 延迟队列消费者——基于插件
 * @date 2022/3/10 19:34
 */
@Slf4j
@Component
public class DelayQueueConsumer {


    // 声明队列监听器,监听死信队列QD
    @RabbitListener(queues = "delayed.queue")
    public void receiveD(Message message){
        String msg = new String(message.getBody());
        log.info("当前时间:{},收到来自死信队列消息:{}",new Date().toString(),msg);
    }

}

1.6.4 测试

依次访问:
http://localhost:8088/ttl/sendTimeAndMsgByPlugins/20000/20sMessage,
http://localhost:8088/ttl/sendTimeAndMsgByPlugins/2000/2sMessage
出现如下结果,之前出现死信的情况消失了。
RabbitMQ——延迟队列_第7张图片

你可能感兴趣的:(消息队列,rabbitmq,java,spring,boot)