Springboot整合RabbitMq

RabbitMq介绍

这里介绍的挺详细的

Spring boot 相关依赖

<dependencies>
        <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-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>
</dependencies>

Spring boot的yml配置

server:
  port: 8080
spring:
  application:
    name: rabbitmq-provider
  rabbitmq:
    host: ip地址
    port: 5672 #注意这个是Java访问rabbit的端口与后台管理的端口不一样
    username: 
    password: 
    virtual-host: /

virtual-host:每一个Rabbit都可以创建很多歌 vhost ,就相当于虚拟主机一样,下面都是一个独立的迷你版的RabbitMq.

Direct类型交换机

Direct类型

Direct类型的Exchange路由规则很简单,它会把消息路由到哪些binding key与routing key完全匹配的Queue中。最基础最简单的模式,发送方把消息发送给订阅方,如果有多个订阅者,默认采取 轮询的方式进行消息发送。

创建一个Direct消息配置类

package com.config.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


/**
 * @Description:
 * @Author:GJM
 * @Date:2023/3/2 15:32
 */
@Configuration
public class DirectRabbitConfig {

    // 创建一个队列
    @Bean
    public Queue TestDirectQueue(){
        // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
        // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
        // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
        // return new Queue("TestDirectQueue",true,true,false);
        //一般设置一下队列的持久化就好,其余两个就是默认fals
        return new Queue("TestDirectQueue",true);
    }
    @Bean
    public Queue TestDirectQueue2(){
        return new Queue("hellorabbitMq",false);
    }

    /*
     * 创建一个Direct交换机
     * @Author:Gjm
     * @Date :2023/3/2 15:39
     * @return
     */
    @Bean
    public DirectExchange TestDirectExchange(){
        return new DirectExchange("TestDirectExchange",true,false);
    }
    //绑定  将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting
    @Bean
    Binding queuebindingDirect() {
        return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
    }


}

service实现发送消息

package com.config.service;

import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;

/**
 * @Description:发送消息服务类
 * @Author:GJM
 * @Date:2023/3/2 15:44
 */
@Service
public class SendMessageService {
    @Autowired
    public RabbitTemplate rabbitTemplate;

    public boolean sendMessage(String messageId, String message, String createDate,String routingKey,String exchange){
        HashMap<Object, Object> params = new HashMap<>();
        params.put("messageId",messageId);
        params.put("message",message);
        params.put("createDate",createDate);
        // 发送到哪个交换机,将数据与routingKey绑定
        try {
            rabbitTemplate.convertAndSend(exchange,routingKey,params);
        }catch (Exception e){
            e.printStackTrace();
        }

        return true;
    }
   
}

创建监听Direct消息队列消费

package com.config.ConsumerListener;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.RabbitListeners;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @Description:消费者监听消息
 * @Author:GJM
 * @Date:2023/3/2 17:01
 */
@Component
public class ConsumerListener {
    @RabbitListener(queues = "TestDirectQueue")
    @RabbitHandler
    public void ConsumerMessage(Map testMessage){

        System.out.println("DirectReceiver消费者收到消息  : " + testMessage.toString());

    }
}

Controller接口

package com.config.Controller;

import com.config.service.SendMessageService;
import org.springframework.amqp.core.Correlation;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.query.Param;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
import java.util.stream.Stream;

/**
 * @Description:
 * @Author:GJM
 * @Date:2023/3/2 15:53
 */
@RestController
public class RabbitDirectSendController {

    @Autowired
    private SendMessageService sendMessageService;

    @PostMapping("/send")
    public void sendRabbitMessage(){
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),"测试一条消息",
                createTime,"TestDirectRouting","TestDirectExchange");
    }
}

主程序启动成功之后,Rabbit后台管理系统会创建一个交换机和消息队列

postman 进行测试

测试结果

ConsumerListener监听打印的消息体

Topic类型交换机

该类型的交换器将所有发送到Topic Exchange的消息被转发到所有RoutingKey中指定的Topic的队列上面。

Exchange将RoutingKey和某Topic进行模糊匹配,其中“”用来匹配一个词,“#”用于匹配一个或者多个词。例如“com.#”能匹配到“com.rabbitmq.oa”和“com.rabbitmq”;而"login."只能匹配到“com.rabbitmq”。

创建Topic消息配置类

package com.config.Topic.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Description:主题交换机
 * @Author:GJM
 * @Date:2023/3/15 16:26
 */
@Configuration
public class TopicRabbitConfig {
    public final static String TQ1 = "TopicQueue.A";
    public final static String TQ2 = "TopicQueue.B";
    public final static String TE = "TopicExchange";
    // TQ1私有的绑定key
    public final static String TR = "topic.A";
    // Tq1 /Tq2 共有的绑定key,#号匹配所有前缀相同的
    public final static String TRP = "topic.#";

    @Bean
    public Queue Tq1(){
        return new Queue(TQ1);
    }
    @Bean
    public Queue Tq2(){
        return new Queue(TQ2);
    }
    @Bean
    public TopicExchange Te(){
        return new TopicExchange(TE,true,false);
    }
    /*
     * 队列与交换机绑定,设置绑定key
     * @Author:Gjm
     * @Date :2023/3/15 17:07
     * @return
     */
    @Bean
    public Binding bindingTqe(){
        return BindingBuilder.bind(Tq1()).to(Te()).with(TR);
    }
    /*
     * 队列与交换机绑定,设置绑定key
     * 只要是topic.开头的rontingKey,这个队列都能接收到消息
     * @Author:Gjm
     * @Date :2023/3/15 17:07
     * @return
     */
    @Bean
    public Binding bindingTqe2(){
        return BindingBuilder.bind(Tq2()).to(Te()).with(TRP);
    }

}

创建监听Topic消息队列消费者

package com.config.ConsumerListener;

import com.config.Topic.config.TopicRabbitConfig;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @Description:监听Topic消息队列并消费
 * @Author:GJM
 * @Date:2023/3/15 17:14
 */
@Component
public class TopicConsumerListener {

    @RabbitListener(queues = TopicRabbitConfig.TQ1)
    @RabbitHandler
    public void getTq1(Map message){
        System.out.println("这是TQ1的消息:" + message.toString());
    }

    @RabbitListener(queues = TopicRabbitConfig.TQ2)
    @RabbitHandler
    public void getTq2(Map message){
        System.out.println("这是TQ2的消息:" + message.toString());
    }

}

Controller接口

这里的SendMessageService类在Direct下已经创建

package com.config.Controller;

import com.config.service.SendMessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

/**
 * @Description:Topic发送消息
 * @Author:GJM
 * @Date:2023/3/15 17:19
 */
@RestController
@RequestMapping("/topic")
public class RabbitTopicSendController {
    @Autowired
    private SendMessageService sendMessageService;

    @GetMapping("/send1")
    public void sendMessage1(){
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),"Topic消息",
                createTime,"topic.A","TopicExchange");
        System.out.println("发送成功");
    }
    @GetMapping("/send2")
    public void sendMessage2(){
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),"Topic消息",
                createTime,"topic.EEE","TopicExchange");
        System.out.println("发送成功");
    }
}

postman 进行测试

调用localhost:8080/topic/send1 接口

预期结果应该是TQ1、TQ2都能收到消息。符合预期

调用localhost:8080/topic/send2 接口

预期结果应该是TQ2能收到消息。符合预期

Fanout类型交换机

扇形类型不处理路由键,会把所有发送到交换器的消息路由到所有绑定的队列中。优点是转发消息最快,性能最好。

创建Fanout消息配置类

package com.config.Fanout.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Description:扇形交换机
 * @Author:GJM
 * @Date:2023/3/15 18:27
 */
@Configuration
public class FanoutRabbitConfig {
    // 定义3个消息队列
    public final static String FQ1 = "FanoutQueue.A";
    public final static String FQ2 = "FanoutQueue.B";
    public final static String FQ3 = "FanoutQueue.C";
    // 定义一个交换机
    public final static String FE = "FanoutExchange";

    @Bean
    public Queue fq1(){
        return new Queue(FQ1,true);
    }
    @Bean
    public Queue fq2(){
        return new Queue(FQ2,true);
    }
    @Bean
    public Queue fq3(){
        return new Queue(FQ3,true);
    }
    @Bean
    public FanoutExchange fe(){
        return new FanoutExchange(FE,true,false);
    }
    /*
     * 将三个消息队列绑定到一个交换机上,不用设置RoutingKey
     * @Author:Gjm
     * @Date :2023/3/15 18:35
     * @return
     */
    @Bean
    public Binding bindingAE(){
        return BindingBuilder.bind(fq1()).to(fe());
    }
    @Bean
    public Binding bindingBE(){
        return BindingBuilder.bind(fq2()).to(fe());
    }
    @Bean
    public Binding bindingCE(){
        return BindingBuilder.bind(fq3()).to(fe());
    }

}

创建监听Fanout消息队列的消费者

package com.config.ConsumerListener;

import com.config.Fanout.config.FanoutRabbitConfig;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

/**
 * @Description:监听Fanout消息队列并消费
 * @Author:GJM
 * @Date:2023/3/15 18:36
 */
@Configuration
public class FanoutConsumerListener {
    @RabbitListener(queues = FanoutRabbitConfig.FQ1)
    @RabbitHandler
    public void getMessageq1(Map message){
        System.out.println("这是FQ1的消息: " + message.toString());
    }
    @RabbitListener(queues = FanoutRabbitConfig.FQ2)
    @RabbitHandler
    public void getMessageq2(Map message){
        System.out.println("这是FQ2的消息: " + message.toString());
    }
    @RabbitListener(queues = FanoutRabbitConfig.FQ3)
    @RabbitHandler
    public void getMessageq3(Map message){
        System.out.println("这是FQ3的消息: " + message.toString());
    }
}

Controller接口

package com.config.Controller;

import com.config.service.SendMessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

/**
 * @Description:Fanout发送消息
 * @Author:GJM
 * @Date:2023/3/15 17:19
 */
@RestController
@RequestMapping("/fanout")
public class RabbitFanoutSendController {
    @Autowired
    private SendMessageService sendMessageService;

    @GetMapping("/send1")
    public void sendMessage1(){
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),"Topic消息",
                createTime,"","FanoutExchange");
        System.out.println("发送成功");
    }

}

postman进行测试

调用localhost:8080/fanout/send1 接口

预期结果应该是3个消息队列都能收到消息。符合预期

创建一个Direct延时队列

延时队列的用处有很多,订单关闭,当达到过期时间才会监听到消息,从而实现业务逻辑。

创建延时消息配置类

package com.config.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

import static com.config.config.TtlRabbitConfig.QUEUE_B;

/**
 * @Description:
 * @Author:GJM
 * @Date:2023/3/3 15:19
 */
@Configuration
public class MyTtlQueueConfig {
    //死信队列配置
    public static final String QUEUE_G = "AG";
    public static final String EXCHANGE_G = "G";

    //声明QG死信队列
    @Bean
    public Queue queueG() {
        return new Queue(QUEUE_G, true);
    }

    // 声明G交换机
    @Bean
    public DirectExchange exchangeG() {
        return new DirectExchange(EXCHANGE_G);
    }

    //死信QG与死信交换机G绑定
    @Bean
    public Binding queueGBindChangeG() {
        return BindingBuilder.bind(queueG()).to(exchangeG()).with("AEG");
    }

    //-----普通队列配置
    public static final String QUEUE_AE = "AE";
    public static final String EXCHANGE_E = "E";

    // 声明一个普通队列AE,将消息投入到死信队列交换机上,设置过期时间
    @Bean
    public Queue queueAE(){
        HashMap<String, Object> arguments = new HashMap<>(3);
        arguments.put("x-dead-letter-exchange", EXCHANGE_G);
        // 设置死信路由键
        arguments.put("x-dead-letter-routing-key", "AEG");
        // 设置过期时间
        arguments.put("x-message-ttl", 20000);

        return new Queue(QUEUE_AE,true,false,false,arguments);
    }
    //灵活设置过期时间

    @Bean
    public DirectExchange exchangeE(){
        return new DirectExchange(EXCHANGE_E,true,false);
    }
    @Bean
    public Binding queueAEBingExchangeE(){
        return BindingBuilder.bind(queueAE()).to(exchangeE()).with("AEE");
    }

}

注意设置死信路由键必须与 死信和死信交换机绑定的路由键一致,不然死信消息队列无法获取消息

arguments.put("x-dead-letter-routing-key", "AEG");

监听消息

package com.config.ConsumerListener;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.annotation.RabbitListeners;
import org.springframework.stereotype.Component;

import java.util.Map;

/**
 * @Description:消费者监听消息
 * @Author:GJM
 * @Date:2023/3/2 17:01
 */
@Component
public class ConsumerListener {
    @RabbitListener(queues = "TestDirectQueue")
    @RabbitHandler
    public void ConsumerMessage(Map testMessage){

        System.out.println("DirectReceiver消费者收到消息  : " + testMessage.toString());

    }
    @RabbitListener(queues = "AG")
    @RabbitHandler
    public void ConsumerTtlAGMessage(Message message){
        System.out.println("AG队列的死信消息 :" + message);
    }

}

Controller 接口

package com.config.Controller;

import com.config.service.SendMessageService;
import org.springframework.amqp.core.Correlation;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.query.Param;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

/**
 * @Description:
 * @Author:GJM
 * @Date:2023/3/2 15:53
 */
@RestController
public class RabbitDirectSendController {

    @Autowired
    private SendMessageService sendMessageService;

    @PostMapping("/send")
    public void sendRabbitMessage(){
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),"测试一条消息",
                createTime,"TestDirectRouting","TestDirectExchange");
    }
    @PostMapping("/sendDieMessage")
    public void sendRabbitDieMessage3(){
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),"测试一条死信消息",
                createTime,"AEE","E");
        System.out.println("发送成功");
    }

}

注意

如果要是改变原有队列的属性,会导致启动时报错,Java设置的属性与MQ已存在的属性不一致错误。需要在后台管理系统中删除。

postman进行接口测试

测试结果

20后收到一条消息,测试成功。

优化延时队列,实现自定义过期时间

MyTtlQueueConfig.class声明一个普通队列AE,将消息投入到死信队列交换机上,设置过期时间改为手动配置

package com.config.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

import static com.config.config.TtlRabbitConfig.QUEUE_B;

/**
 * @Description:
 * @Author:GJM
 * @Date:2023/3/3 15:19
 */
@Configuration
public class MyTtlQueueConfig {
    //死信队列配置
    public static final String QUEUE_G = "AG";
    public static final String EXCHANGE_G = "G";

    //声明QG死信队列
    @Bean
    public Queue queueG() {
        return new Queue(QUEUE_G, true);
    }

    // 声明G交换机
    @Bean
    public DirectExchange exchangeG() {
        return new DirectExchange(EXCHANGE_G);
    }

    //死信QG与死信交换机G绑定
    @Bean
    public Binding queueGBindChangeG() {
        return BindingBuilder.bind(queueG()).to(exchangeG()).with("AEG");
    }

    //-----普通队列配置
    public static final String QUEUE_AE = "AE";
    public static final String EXCHANGE_E = "E";

    // 声明一个普通队列AE,将消息投入到死信队列交换机上,设置过期时间
   /*@Bean
    public Queue queueAE(){
        HashMap<String, Object> arguments = new HashMap<>(3);
        arguments.put("x-dead-letter-exchange", EXCHANGE_G);
        // 设置死信路由键
        arguments.put("x-dead-letter-routing-key", "AEG");
        // 设置过期时间
        arguments.put("x-message-ttl", 20000);

        return new Queue(QUEUE_AE,true,false,false,arguments);
    }*/
    //灵活设置过期时间
    @Bean
    public Queue queueAE(){
        HashMap<String, Object> arguments = new HashMap<>(3);
        arguments.put("x-dead-letter-exchange", EXCHANGE_G);
        // 设置死信路由键
        arguments.put("x-dead-letter-routing-key", "AEG");

        return new Queue(QUEUE_AE,true,false,false,arguments);
    }
    @Bean
    public DirectExchange exchangeE(){
        return new DirectExchange(EXCHANGE_E,true,false);
    }
    @Bean
    public Binding queueAEBingExchangeE(){
        return BindingBuilder.bind(queueAE()).to(exchangeE()).with("AEE");
    }

}

Service实现发送接口增加一种发送类型

package com.config.service;

import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;

/**
 * @Description:发送消息服务类
 * @Author:GJM
 * @Date:2023/3/2 15:44
 */
@Service
public class SendMessageService {
    @Autowired
    public RabbitTemplate rabbitTemplate;

    public boolean sendMessage(String messageId, String message, String createDate,String routingKey,String exchange){
        HashMap<Object, Object> params = new HashMap<>();
        params.put("messageId",messageId);
        params.put("message",message);
        params.put("createDate",createDate);
        // 发送到哪个交换机,将数据与routingKey绑定
        try {
            rabbitTemplate.convertAndSend(exchange,routingKey,params);
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }

        return true;
    }
    public boolean sendMessage(String messageId, String message, String createDate, String routingKey, String exchange, MessagePostProcessor messagePostProcessor){
        HashMap<Object, Object> params = new HashMap<>();
        params.put("messageId",messageId);
        params.put("message",message);
        params.put("createDate",createDate);
        // 发送到哪个交换机,将数据与routingKey绑定
        try {
            rabbitTemplate.convertAndSend(exchange,routingKey,params,messagePostProcessor);
        }catch (Exception e){
            e.printStackTrace();
        }

        return true;
    }
}

Controller进行代码优化,自定义设置过期时间

 // 优化自己设置过期时间灵活
    @PostMapping("/sendTtl")
    public void sendRabbitDieMessage4(@Param("ttl") String ttl,@Param("messages") String messages){
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),messages,
                createTime,"AEE","E" ,(message)->{
                    message.getMessageProperties().setExpiration(ttl);
                    return message;
                });
        System.out.println("发送成功");
    }

postmen测试即可。

RabbitMq延时队列的缺点

如果同事设置2条消息,第一条设置30s,第二条设置10s,na那么则会在30s后获取到两天条数据。

因为myq只会检查第一条消息的过期时间。

localhost:8080/sendDieMessage?messages=这条消息过期时间是20s&ttl=20000

localhost:8080/sendDieMessage?messages=这条消息过期时间是10s&ttl=10000

测试结果:发现20s,10s的一起消费啦。

使用x-delayed-message插件弥补缺点

Git下载插件

将插件上传到服务器/usr/lib/rabbitmq/lib/rabbitmq_server-3.10.0/plugins目录下。

运行rabbitmq-plugins list,可以查看当前rabbitMq的插件列表。

root@VM-4-4-centos plugins]# rabbitmq-plugins list
Listing plugins with pattern ".*" ...
 Configured: E = explicitly enabled; e = implicitly enabled
 | Status: * = running on rabbit@VM-4-4-centos
 |/
[  ] rabbitmq_amqp1_0                  3.10.0
[  ] rabbitmq_auth_backend_cache       3.10.0
[  ] rabbitmq_auth_backend_http        3.10.0
[  ] rabbitmq_auth_backend_ldap        3.10.0
[  ] rabbitmq_auth_backend_oauth2      3.10.0
[  ] rabbitmq_auth_mechanism_ssl       3.10.0
[  ] rabbitmq_consistent_hash_exchange 3.10.0
[  ] rabbitmq_delayed_message_exchange 3.10.2
[  ] rabbitmq_event_exchange           3.10.0
[  ] rabbitmq_federation               3.10.0
[  ] rabbitmq_federation_management    3.10.0
[  ] rabbitmq_jms_topic_exchange       3.10.0
[E*] rabbitmq_management               3.10.0
[e*] rabbitmq_management_agent         3.10.0
[  ] rabbitmq_mqtt                     3.10.0
[  ] rabbitmq_peer_discovery_aws       3.10.0
[  ] rabbitmq_peer_discovery_common    3.10.0
[  ] rabbitmq_peer_discovery_consul    3.10.0
[  ] rabbitmq_peer_discovery_etcd      3.10.0
[  ] rabbitmq_peer_discovery_k8s       3.10.0
[  ] rabbitmq_prometheus               3.10.0
[  ] rabbitmq_random_exchange          3.10.0
[  ] rabbitmq_recent_history_exchange  3.10.0
[  ] rabbitmq_sharding                 3.10.0
[  ] rabbitmq_shovel                   3.10.0
[  ] rabbitmq_shovel_management        3.10.0
[  ] rabbitmq_stomp                    3.10.0
[  ] rabbitmq_stream                   3.10.0
[  ] rabbitmq_stream_management        3.10.0
[  ] rabbitmq_top                      3.10.0
[  ] rabbitmq_tracing                  3.10.0
[  ] rabbitmq_trust_store              3.10.0
[e*] rabbitmq_web_dispatch             3.10.0
[  ] rabbitmq_web_mqtt                 3.10.0
[  ] rabbitmq_web_mqtt_examples        3.10.0
[  ] rabbitmq_web_stomp                3.10.0
[  ] rabbitmq_web_stomp_examples       3.10.0
[root@VM-4-4-centos plugins]# 

启动插件rabbitmq-plugins enable rabbitmq_delayed_message_exchange

[root@VM-4-4-centos plugins]# rabbitmq-plugins enable rabbitmq_delayed_message_exchange
Enabling plugins on node rabbit@VM-4-4-centos:
rabbitmq_delayed_message_exchange
The following plugins have been configured:
  rabbitmq_delayed_message_exchange
  rabbitmq_management
  rabbitmq_management_agent
  rabbitmq_web_dispatch
Applying plugin configuration to rabbit@VM-4-4-centos...
The following plugins have been enabled:
  rabbitmq_delayed_message_exchange

started 1 plugins.
[root@VM-4-4-centos plugins]# 

在RabbitMq的后台页面可以看到插件,说明成功啦。升级RabbitMQ时,必须冲新安装该插件,也就是要安装它们的新版本;或者,可以在升级之前或升级期间禁用它们。

下面是Java代码实现,

  1. 定义交换机,队列

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

/**
 * @Description:引用RabbitMq的rabbitmq_delayed_message_exchange插件实现延时队列
 * @Author:GJM
 * @Date:2023/3/9 18:45
 */
@Configuration
public class MyTtlQueueConfig2 {
    // 延时队列名称
    public static final String DELAYED_QUEUE_NAME = "delayed.queue";
    // 延时队列交换机
    public static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";
    // 延时路由
    public static final String DELAYED_ROUTING_KEY = "delayed.routingKey";
    @Bean
    public Queue delayedQueueA(){
        return new Queue(DELAYED_QUEUE_NAME,true);
    }
    /**
     * 自定义交换机 定义一个延迟交换机
     *  不需要死信交换机和死信队列,支持消息延迟投递,消息投递之后没有到达投递时间,是不会投递给队列
     *  而是存储在一个分布式表,当投递时间到达,才会投递到目标队列
     * @return
     */
    @Bean
    public CustomExchange delayedExchangeA(){
        Map<String, Object> args = new HashMap<>(1);
        // 自定义交换机的类型
        args.put("x-delayed-type", "direct");
        return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message", true, false, args);
    }
    @Bean
    public Binding delayedQueueBindExchange(){
        return BindingBuilder.bind(delayedQueueA()).to(delayedExchangeA()).with(DELAYED_ROUTING_KEY).noargs();
    }
}
  1. 消费者监听配置

package com.config.ConsumerListener;

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

import java.io.IOException;
import java.util.Map;

/**
 * @Description:消费者监听消息
 * @Author:GJM
 * @Date:2023/3/2 17:01
 */
@Component
@Slf4j
public class ConsumerListener {
    /*
     * 使用延时插件优化后的消息队列
     * @Author:Gjm
     * @Date :2023/3/9 19:10
     * @return
     */
    @RabbitListener(queues = "delayed.queue")
    public void  ConsumerTtlMessage3(Message message){
        System.out.println("使用延时插件优化后的消息队列 :" + message);
    }

}
  1. 消息生产者

    /*
     * 使用延时插件的消息队列发送消息
     * @Author:Gjm
     * @Date :2023/3/9 19:12
     * @return
     */
    @PostMapping("/sendDelayedMessage")
    public void sendDelayedMessage(@Param("ttl") Integer ttl){
        String createTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        StringBuilder messages = new StringBuilder("这条消息过期时间是");
        messages.append(ttl).append("秒");

        sendMessageService.sendMessage(String.valueOf(UUID.randomUUID()),messages,
                createTime,"delayed.routingKey","delayed.exchange" ,(message)->{
                    message.getMessageProperties().setDelay(ttl);
                    return message;
                });

        System.out.println("发送成功");
    }
  1. 重新启动,postman多次设置不一样的过期时间测试。

  1. 测试结果符合预期,先到时间先消费。

实现消息手动确认

手动确认 , 这个比较关键,也是我们配置接收消息确认机制时,多数选择的模式。 消费者收到消息后,手动调用basic.ack/basic.nack/basic.reject后,RabbitMQ收到这些消息后,才认为本次投递成功。

yml配置改动

  rabbitmq:
    host: 
    port: 5672
    username: 
    password: 
    virtual-host: /
    listener:
      simple:
        # 表示消费者消费成功消息以后需要手工的进行签收(ack确认),默认为 auto
        acknowledge-mode: manual
        default-requeue-rejected: true

消费者监听改动

/**
 * @Description:消费者监听消息
 * @Author:GJM
 * @Date:2023/3/2 17:01
 */
@Component
@Slf4j
public class ConsumerListener {
    @RabbitListener(queues = "TestDirectQueue")
    @RabbitHandler
    public void ConsumerMessage(Message msg, Channel channel){

        System.out.println("DirectReceiver消费者收到消息  : " + msg.toString());
        Map<String,Object> message = msg.getMessageProperties().getHeader("param");
        long deliverTag = msg.getMessageProperties().getDeliveryTag();
       try {
            //业务逻辑代码...
            //消费成功,确认消息,
            // 第一个参数:deliveryTag:该消息的index
            // 第二个参数:是否批量.true:将一次性拒绝所有小于deliveryTag的消息。
            channel.basicAck(deliverTag, true);
        }catch (Exception e) {
            try {
                //出现异常用的,
                //第一个参数:deliveryTag:该消息的index
                // 第二个参数:是否批量.true:将一次性拒绝所有小于deliveryTag的消息。
                //第三个参数:被拒绝的是否重新入队列
                // 但是一定要有一个拒绝策略,比如重试5次,记录到Redis,后期进行补偿,然后丢掉这个消息,不然会一直循环这个消费
                channel.basicNack(deliverTag, false, true);
            } catch (IOException ioException) {
                log.error("重新放入队列失败,失败原因:{}", e.getMessage(), e);
            }
    }
}

如果异常导致的拒绝,设置重新入队之后,异常没有解决,会一直循环错误。建议对异常错误有补偿策略。比如重试5次,记录到Redis,后期进行补偿,然后丢掉这个消息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值