RabbitMQ整合SpringBoor以及RabbitMQ的延迟队列

延迟队列的概念

前面介绍了死信队列,针对于ttl进入死信队列的情况,假如我们把前面的消费者一关闭,然后对所有的消息都进行设置过期时间,这样是不是就形成了一个延迟队列了?

使用场景:比如订单超时关闭,假如我们使用定时任务,假如数据量很大的情况下肯定会存在问题,因为需要检查是否达到定时时间,这么大的数据量,肯定会很占用时间,所以这个时候使用延迟队列就比较合适了,

创建SpringBoot项目

创建SpringBoot工程,添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.8</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.dongmu.rabbitmq</groupId>
    <artifactId>springboot-rabbitmq</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-rabbitmq</name>
    <description>springboot-rabbitmq</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--RabbitMQ 依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
        <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--RabbitMQ 测试依赖-->
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

在配置文件中添加,里面写自己的东西。

spring.rabbitmq.host=**.***.***.***
spring.rabbitmq.port=5672
spring.rabbitmq.username=****
spring.rabbitmq.password=***

下面我们实现下面这个场景
RabbitMQ整合SpringBoor以及RabbitMQ的延迟队列_第1张图片
编写配置类

package com.dongmu.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;

@Configuration

public class RabbitMqTTLConfig {


    //普通交换机名称
    public static final String NORMAL_EXHANGE = "X";
    //死信交换机名称
    public static final String DEAD_EXHANGE = "Y";
    //普通队列名称
    public static final String NORMAL_QUEUEA = "QA";
    public static final String NORMAL_QUEUEB = "QB";
    //死信队列名称
    public static final String DEAD_QUEUED = "QD";
    
//    设置两个交换机

    @Bean("xExchange")
    public DirectExchange xExchange(){
        return new DirectExchange(NORMAL_EXHANGE);
    }

    @Bean("yExchange")
    public DirectExchange yExchange(){
        return new DirectExchange(DEAD_EXHANGE);
    }

    
    //分别创建三个队列
    @Bean("queueA")
    public Queue queueA(){
        Map<String,Object> stringObjectMap = new HashMap<>(3);
        //设置死信交换机
        stringObjectMap.put("x-dead-letter-exchange",DEAD_EXHANGE);
        //设置死信队列
        stringObjectMap.put("x-dead-letter-routing-key","YD");
        //设置TTL
        stringObjectMap.put("x-message-ttl",10000);

        return QueueBuilder.durable(NORMAL_QUEUEA).withArguments(stringObjectMap).build();
    }

    @Bean("queueB")
    public Queue queueB(){
        Map<String,Object> stringObjectMap = new HashMap<>(3);
        //设置死信交换机
        stringObjectMap.put("x-dead-letter-exchange",DEAD_EXHANGE);
        //设置死信队列
        stringObjectMap.put("x-dead-letter-routing-key","YD");
        //设置TTL
        stringObjectMap.put("x-message-ttl",40000);

        return QueueBuilder.durable(NORMAL_QUEUEB).withArguments(stringObjectMap).build();
    }

    @Bean("queueD")
    public Queue queueD(){
        return QueueBuilder.durable(DEAD_QUEUED).build();
    }

    
    //把队列和交换机进行绑定,并且设置routingkey
    @Bean
    public Binding queueA_X(@Qualifier("queueA") Queue queueA,@Qualifier("xExchange") DirectExchange xExchange){
        return BindingBuilder.bind(queueA).to(xExchange).with("XA");
    }

    @Bean
    public Binding queueB_X(@Qualifier("queueB") Queue queueB,@Qualifier("xExchange") DirectExchange xExchange){
        return BindingBuilder.bind(queueB).to(xExchange).with("XB");
    }

    @Bean
    public Binding queueD_Y(@Qualifier("queueD") Queue queueD,@Qualifier("yExchange") DirectExchange yExchange){
        return BindingBuilder.bind(queueD).to(yExchange).with("YD");
    }
}

写发送消息的Controller

package com.dongmu.rabbitmq.controller;


import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@RestController
@Slf4j
@RequestMapping("/ttl")
public class SendMsgController {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/sendmsg/{message}")
    public void sendMsg (@PathVariable("message") String message){
        log.info("当前时间:{},发送一条消息给两个ttl队列:{}",new Date().toString(),message);
        rabbitTemplate.convertAndSend("X","XA","消息来自ttl为10秒:"+message);
        rabbitTemplate.convertAndSend("X","XB","消息来自ttl为40秒:"+message);

    }
}

写处理延时消息的消费者

package com.dongmu.rabbitmq.controller;


import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.Date;

@Slf4j
@Component
public class DeadQueueConsumerController {

    @RabbitListener(queues = "QD")
    public void receiveD(Message message, Channel channel){
        String msg = new String(message.getBody(), StandardCharsets.UTF_8);
        log.info("当前时间:{},发送一条消息给两个ttl队列:{}结束。",new Date().toString(),msg);
    }

}

结果
RabbitMQ整合SpringBoor以及RabbitMQ的延迟队列_第2张图片
RabbitMQ整合SpringBoor以及RabbitMQ的延迟队列_第3张图片
可以发现延时队列实现成功。

延迟队列优化

我们上面的队列都是每个队列设置一个消息的超时时间,所以我们每设置一个延时任务都需要重新设置一个队列,这样肯定是不合理的。如何进行优化呢?

我们可以设置一个队列不设置ttl
RabbitMQ整合SpringBoor以及RabbitMQ的延迟队列_第4张图片

添加队列

public static final String NORMAL_QUEUEC = "QC";

@Bean("queueC")
    public Queue queueC(){
        Map<String,Object> stringObjectMap = new HashMap<>();
        //设置死信交换机
        stringObjectMap.put("x-dead-letter-exchange",DEAD_EXHANGE);
        //设置死信队列
        stringObjectMap.put("x-dead-letter-routing-key","YD");

        return QueueBuilder.durable(NORMAL_QUEUEC).withArguments(stringObjectMap).build();
    }

将队列和交换机进行绑定

@Bean
    public Binding queueC_X(@Qualifier("queueC") Queue queueC,@Qualifier("xExchange") DirectExchange xExchange){
        return BindingBuilder.bind(queueC).to(xExchange).with("XC");
    }

编写Controller层在交互的时候设置过期时间,这样就就可以做到同一个队列实现不同的延迟任务了

@GetMapping("/sendExpirationMsg/{message}/{ttltime}")
    public void sendExpirationMsg (@PathVariable("message") String message,@PathVariable("ttltime") String ttltime){
        log.info("当前时间:{},发送一条延迟消息给普通ttl队列C:{},时长是"+ttltime+"毫秒。",new Date().toString(),message);
        rabbitTemplate.convertAndSend("X","XC","消息来自ttlC时间为"+ttltime+"毫秒:"+message,mesg->{
            mesg.getMessageProperties().setExpiration(ttltime);
            return mesg;
        });

    }

访问测试
在这里插入图片描述
在这里插入图片描述

可以发现访问测试成功。

但是还是存在问题的,假如我很快地发送两条消息一个20秒,一个两秒,效果如下
在这里插入图片描述

可以发现这个延迟任务时间到了,但是队列前面还有元素,它会等待队列前面的元素也出队了之后才会进入死信队列。

所以这种方式虽然只需要一个队列,但是这个方式就无法保证过期的消息立马执行。也就是只能检测第一条消息是否到期。怎么解决呢?

采用延迟队列的方式

在官网上下载插件 ,rabbitmq_delayed_message_exchange 插件,然后解压放置到 RabbitMQ 的插件目录。

RabbitMQ整合SpringBoor以及RabbitMQ的延迟队列_第5张图片

  • 安装方式

把插件拷贝在我们rabbitmq的安装目录中的一个文件夹下

[root@iZwz9hv1phm24s3jicy8x1Z rabbitmq]# ll
total 34844
-rw-r--r-- 1 root root 18850824 May 15 22:48 erlang-21.3-1.el7.x86_64.rpm
-rw-r--r-- 1 root root  1254680 Jun  1 22:02 openssl-libs-1.0.2k-19.el7.x86_64.rpm
-rw-r--r-- 1 root root    43377 May 15 22:48 rabbitmq_delayed_message_exchange-3.8.0.ez
-rw-r--r-- 1 root root 15520399 May 15 22:48 rabbitmq-server-3.8.8-1.el7.noarch.rpm
[root@iZwz9hv1phm24s3jicy8x1Z rabbitmq]# cp rabbitmq_delayed_message_exchange-3.8.0.ez /usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins
[root@iZwz9hv1phm24s3jicy8x1Z rabbitmq]# cd /usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins
[root@iZwz9hv1phm24s3jicy8x1Z plugins]# /sbin/service rabbitmq-server restart
Redirecting to /bin/systemctl restart rabbitmq-server.service

重新启动之后我们就可以在服务器中的可视化界面里面看到
RabbitMQ整合SpringBoor以及RabbitMQ的延迟队列_第6张图片
这个延迟消息的实现是由交换机来实现为不是通过队列来实现的了。
交换机来负责延迟消息,延迟时间到了之后才会把消息传递给队列。

  • 具体实现

RabbitMQ整合SpringBoor以及RabbitMQ的延迟队列_第7张图片
编写配置类

package com.dongmu.rabbitmq.config;


import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.CustomExchange;
import org.springframework.amqp.core.Queue;
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;

@Configuration
public class DelayQueueConfig {
    //延迟队列名称
    public static final String DELAYED_QUEUE_NAME = "delayed.queue";
    //延迟交换机
    public static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";
    //延迟routingkey
    public static final String DELAYED_ROUTING_KEY = "delayed.routingkey";

    @Bean
    public Queue delayedQueue() {
        return new Queue(DELAYED_QUEUE_NAME);
    }
    //自定义交换机 我们在这里定义的是一个延迟交换机
    @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_NAME, "x-delayed-message", true, false,
                args);
    }
    @Bean
    public Binding bindingDelayedQueue(@Qualifier("delayedQueue") Queue queue,
                                       @Qualifier("delayedExchange") CustomExchange
                                               delayedExchange) {
        return
                BindingBuilder.bind(queue).to(delayedExchange).with(DELAYED_ROUTING_KEY).noargs();
    }
}

编写Controller代码

 @GetMapping("/sendDelayMsg/{message}/{ttltime}")
    public void sendDelayMsg (@PathVariable("message") String message,@PathVariable("ttltime") int ttltime){
        log.info("当前时间:{},发送一条延迟消息给延迟交换机:{},时长是"+ttltime+"毫秒。",new Date().toString(),message);
        rabbitTemplate.convertAndSend(DelayQueueConfig.DELAYED_EXCHANGE_NAME,DelayQueueConfig.DELAYED_ROUTING_KEY,
                "消息来自ttlC时间为"+ttltime+"毫秒:"+message, mesg->{
            mesg.getMessageProperties().setDelay(ttltime);
            return mesg;
        });
    }

编写消费者代码

@RabbitListener(queues = DelayQueueConfig.DELAYED_QUEUE_NAME)
    public void receiveDelayQueue(Message message, Channel channel){
        String msg = new String(message.getBody(), StandardCharsets.UTF_8);
        log.info("当前时间:{},收到延迟交换机发送给队列的消息:{}结束。",new Date().toString(),msg);
    }

然后我们继续同时发送两条消息可以发现上面的问题已经解决了
RabbitMQ整合SpringBoor以及RabbitMQ的延迟队列_第8张图片
所以总的来说

  • 第一种方式创建队列很麻烦
  • 第二种存在延迟时间到了无法处理的问题
  • 第三种基于插件的实现方式

你可能感兴趣的:(RabbitMQ,rabbitmq,spring,boot,java)