SpringBoot整合RabbitMQ

RabbitMq的后台管理信息

地址:http://localhost:15672/#/queues

默认账号密码:guest guest

Overview模块可以看到监听端口信息和访问web的端口

Exchanges模块可以看到配置的交换机

Queques and Streams模块可以看到配置的消息队列

Admin模块可以看到用户信息 添加修改用户

项目配置

引入依赖

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

配置文件

spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    # 虚拟主机 虚拟host 可以不设置,使用server默认host
    virtual-host: /
    username: guest
    password: guest

0.使用DirectRabbitConfig类

1.创建DirectRabbitConfig类 创建消息队列和交换机最后将两者绑定

package com.cn.controller;

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;

/**
 * @Author : JCccc
 * @CreateTime : 2019/9/3
 * @Description :
 **/
@Configuration
public class DirectRabbitConfig {

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

        //一般设置一下队列的持久化就好,其余两个就是默认false
        return new Queue("TestDirectQueue",true);
    }

    //Direct交换机 起名:TestDirectExchange
    @Bean
    DirectExchange TestDirectExchange() {
        //  return new DirectExchange("TestDirectExchange",true,true);
        return new DirectExchange("TestDirectExchange",true,false);
    }

    //绑定  将队列和交换机绑定, 并设置用于匹配键:TestDirectRouting
    @Bean
    Binding bindingDirect() {
        return BindingBuilder.bind(TestDirectQueue()).to(TestDirectExchange()).with("TestDirectRouting");
    }

    @Bean
    DirectExchange lonelyDirectExchange() {
        return new DirectExchange("lonelyDirectExchange");
    }

}

2.创建生产者

@GetMapping("addQue")
public String addQue(@Param("mess") String mess ){
    // 交换机,绑定键,消息
   rabbitTemplate.convertAndSend("TestDirectExchange","TestDirectRouting",mess+"111");
   return "成功";
}

3.创建消费者

//监听队列
@RabbitListener(queues = "TestDirectQueue")
public void getque(String message){
    System.out.println("getque:"+message);
}

1.简单消息队列模型

1.在管理后台创建一个队列 设置名称:testqueque

2.编写生产者测试类SpringAmqpTest,并利用 RabbitTemplate 实现消息发送

@RestController
public class testController {
   @Autowired
   private RabbitTemplate rabbitTemplate;

   @GetMapping("addQue")
   public String addQue(@Param("mess") String mess ){
      rabbitTemplate.convertAndSend( "testqueque",mess);
      return "成功";
   }
}

3.编写消费者,监听队列消息

@RabbitListener(queues = "testqueque")
public void getque2(String message){
    System.out.println("getque2:"+message);
}

2.Work工作队列模型

1生产者

@GetMapping("addQue")
public String addQue(@Param("mess") String mess ){
    String queueName = "work.queue";
    String message = "Hello SpringAQMP-";
    for (int i = 0; i < 20; i++) {
        template.convertAndSend(queueName,message+i);
    }
}

消费者

@RabbitListener(queues = "work.queue")
public void workQueueListener01(String message) throws InterruptedException {
    System.out.println("消费者01接收到消息:" + message + " - " + LocalTime.now());
    Thread.sleep(20);
}
@SneakyThrows
@RabbitListener(queues = "work.queue")
public void workQueueListener02(String message){
    System.out.println("消费者02接收到消息:" + message + " - " + LocalTime.now());

正常情况下每个消费者要消费的消息数量是一样的。消息是平均分配给每个消费者,并没有考虑到消费者的处理能力。这样显然不合理,因此修改消费者的配置文件使得每个消费者按照能力顺序处理信息

spring:
  rabbitmq:
    listener:
      simple: # 简单队列和work队列的配置
        prefetch: 1 # 每次只能获取一条消息,处理完成才获取下一条消息

3.发布订阅模型

在发布订阅模型中 有四个角色

▪**producer:**生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)

▪**exchange:**交换机,一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于exchange的类型。exchange 有以下3种类型:

​ ▫fanout:广播,将消息交给所有绑定到交换机的队列
​ ▫direct: 定向,把消息交给符合指定 routing key 的队列
​ ▫topic: 通配符,把消息交给符合 routing pattern 的队列
▪**consumer:**消费者,订阅队列

▪**queue:**消息队列,接收消息、缓存消息。

3.1.Fanout广播

(一条消息发送到交换机后所有与该交换机绑定的队列都可以监听到)

在广播模式下,消息发送流程是这样的:

▫ 可以有多个消费者

▫ 每个消费者有自己的queue(队列)

▫ 每个队列都要绑定到Exchange(交换机)

▫ 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。

▫ 交换机把消息发送给绑定过的所有队列

▫ 队列的消费者都能拿到消息。实现一条消息被多个消费者消费

▫ 使用内置的 amq.fanout 交换机

▫ 两个队列不用手动创建

1创建队列fanout.queue01、fanout.queue02 与默认交换机amq.fanout 绑定

或者与创建的FanoutExchange交换机绑定

1生产者

@GetMapping("addQue")
public String addQue(@Param("mess") String mess ){
    rabbitTemplate.convertAndSend("amq.fanout", "", "Hello amq.fanout");
}

2消费者

@Component
public class SpringRabbitListener {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "fanout.queue01"),
            exchange = @Exchange(name = "amq.fanout", type = ExchangeTypes.FANOUT)
    ))
    public void fanoutQueueListener01(String message) {
        System.out.println("消费者01接收到fanout.queue01的消息:" + message);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "fanout.queue02"),//使用@Queue(name = "fanout.queue01")RabbitMQ自动生成队列
            exchange = @Exchange(name = "amq.fanout", type = ExchangeTypes.FANOUT)
    ))
    public void fanoutQueueListener02(String message) {
        System.out.println("消费者02接收到fanout.queue02的消息:" + message);
    }
} 

3.2.Direct路由

默认amq.direct交换机 或者创建DirectExchange交换机

在 Fanout 模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到 direct 类型的 exchange。

Direct模型下:

▫ 队列与交换机的绑定要指定一个 RoutingKey
▫ 消息的发送方在向 exchange 发送消息时,也必须指定消息的 RoutingKey。
▫ exchange 不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey 与消息的 Routing key 完全一致,才会接收到消息

1生产者

@GetMapping("addQue")
public String addQue(@Param("mess") String mess ){
    rabbitTemplate.convertAndSend("amq.direct", "save", "新增通知");
    rabbitTemplate.convertAndSend("amq.direct", "delete", "删除通知");
}

2消费者

@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "direct.queue01"),
    exchange = @Exchange(name = "amq.direct", type = ExchangeTypes.DIRECT),
    key = {"save"}
))
// 处理新增的业务
public void directQueueListener01(String message){
    System.out.println("消费者01接收到direct.queue01的消息:" + message);
}

@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "direct.queue02"),
    exchange = @Exchange(name = "amq.direct", type = ExchangeTypes.DIRECT),
    key = {"delete"}
))
// 处理删除的业务
public void directQueueListener02(String message){
    System.out.println("消费者02接收到direct.queue02的消息:" + message);
}

3.3.Topics通配符

默认amq.topic 或者创建TopicExchange交换机

Topics 类型的 Exchange 与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。

不同的是 Topic类型的 Exchange 可以让队列在绑定 Routingkey 的时候使用通配符

通配符规则:

​ #:匹配一个或多个单词

​ *:匹配一个单词

举例:

​ user.#:能够匹配 user.add 或者 user.detail.add

​ user.*:只能匹配 user.add

生产者

@GetMapping("addQue")
public String addQue(@Param("mess") String mess ){
    rabbitTemplate.convertAndSend("amq.topic", "user.add", "新增用户通知");
    rabbitTemplate.convertAndSend("amq.topic", "user.update", "更新户通知");
    rabbitTemplate.convertAndSend("amq.topic", "dept.add", "新增部门通知");
    rabbitTemplate.convertAndSend("amq.topic", "dept.update", "更新部门通知");
}

消费者

@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "topic.queue01"),
    exchange = @Exchange(name = "amq.topic", type = ExchangeTypes.TOPIC),
    key = "user.*"
))
public void topicQueueListener01(String message){
    System.out.println("消费者01接收到topic.queue01的消息:" + message);
}

@RabbitListener(bindings = @QueueBinding(
    value = @Queue(name = "topic.queue02"),
    exchange = @Exchange(name = "amq.topic", type = ExchangeTypes.TOPIC),
    key = "dept.*"
))
public void topicQueueListener02(String message){
    System.out.println("消费者02接收到topic.queue02的消息:" + message);
}

生产者可靠性

生产者重连

有时由于网络波动,可能出现客户端连接mq失败的情况,通过配置可以开始连接失败的重连机制

spring:
  rabbitmq:
    connection-timeout: 1s # 设置MQ的连接超时时间
    template:
      retry:
        enabled: true # 开启超时重试机制
        initial-interval: 1000ms # 失败后的初始等待时间
        multiplier: 1 # 失败后下次的等待时长倍数,下次等待时长 = initial-interval * multiplier
        max-attempts: 3 # 最大重试次数

生产者确认

包括生产者发送消息成功,消费者接收消息成功

server:
  port: 8021
spring:
  #给项目来个名字
  application:
    name: rabbitmq-provider
  #配置rabbitMq 服务器
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: root
    password: root
    #虚拟host 可以不设置,使用server默认host
    virtual-host: JCcccHost
      #确认消息已发送到交换机(Exchange)
    publisher-confirms: true
    #publisher-confirm-type: correlated
      #确认消息已发送到队列(Queue)
    publisher-returns: true

如果在配置确认回调,测试发现无法触发回调函数,那么存在原因也许是因为版本导致的配置项不起效,可以把publisher-confirms: true替换为 publisher-confirm-type: correlated

创建RabbitTemplateConfig

package com.cn.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@Slf4j
public class RabbitTemplateConfig {

    @Bean
    public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMandatory(true);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        template.setEncoding("utf-8");

        //实现消息发送到exchange后接收ack回调,publisher-confirms:true
        //如果队列是可持久化的,则在消息成功持久化之后生产者收到确认消息
        template.setConfirmCallback(((correlationData, ack, cause) -> {

            if(ack) {
                log.info("消息成功发送到exchange,id:{}", correlationData.getId());
            } else {
                /*
                 * 消息未被投放到对应的消费者队列,可能的原因:
                 * 1)发送时在未找到exchange,例如exchange参数书写错误
                 * 2)消息队列已达最大长度限制(声明队列时可配置队列的最大限制),此时
                 * 返回的cause为null。
                 */
                log.info("******************************************************");
                log.info("11消息发送失败: {}", cause);
            }
        }));

        //消息发送失败返回队列,publisher-returns:true
        template.setMandatory(true);

        //实现消息发送的exchange,但没有相应的队列于交换机绑定时的回调
        template.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
            String id = message.getMessageProperties().getCorrelationId();
            log.info("消息:{} 发送失败, 应答码:{} 原因:{} 交换机: {}  路由键: {}", id, replyCode, replyText, exchange, routingKey);
        });

        return template;
    }

}

发送请求时增加一个uuid

@GetMapping("addQue")
public String addQue(@Param("mess") String mess ){
   CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
   rabbitTemplate.convertAndSend("amq.fanout",null,mess+"111",correlationData);
   return "成功";
}
  • ConfirmCallback为发送Exchange(交换器)时回调,成功或者失败都会触发;
  • ReturnCallback为路由不到队列时触发,成功则不触发;

RabbitMQ提供了Publisher Confirm和Publisher Return两种确认机制。开启确机制认后,在MQ成功收到消息后会返回确认消息给生产者。返回的结果有以下几种情况:

  • 消息投递到了交换机,但是路由失败(routingKey不对,找不到对应的MQ)。此时会通过Publisher Return返回路由异常原因,然后返回ACK,告知投递成功。
  • 临时消息投递到了MQ,并且入队成功,返回ACK,告知投递成功。
  • 持久消息投递到了MQ,并且入队完成持久化(保存到磁盘),返回ACK ,告知投递成功。
  • 其它情况都会返回NACK,告知投递失败。

生产者确认需要额外的网络和系统资源开销,尽量不要使用。

如果一定要使用,无需开启Publisher-Return机制,因为一般路由失败是自己业务问题。
对于nack消息可以有限次数重试,依然失败则记录异常消息。

MQ的可靠性

在默认情况下,RabbitMQ会将接收到的信息保存在内存中以降低消息收发的延迟。这样会导致两个问题:

1、一旦MQ宕机,内存中的消息会丢失。

2、内存空间有限,当消费者故障或处理过慢时,会导致消息积压,引发MQ阻塞。

数据持久化

RabbitMQ实现数据持久化包括3个方面:

交换机持久化(Durable属性,Spring默认设置为Durable);

队列持久化(Durable属性,Spring默认设置为Durable);

消息持久化(发送消息时设置delivery_mode=2persisent,Spring发送的消息默认是持久化的)

In memory Persistent Transient, Paged Out
内存 持久

Lazy Queue

从RabbitMQ的3.6.0版本开始,就增加了Lazy Queue的概念,也就是惰性队列。

惰性队列的特征如下:
1、接收到消息后直接存入磁盘而非内存(内存中只保留最近的消息,默认2048条)。

2、消费者要消费消息时才会从磁盘中读取并加载到内存。
3、支持数百万条的消息存储。
4、在3.12版本后,所有队列都是Lazy Queue模式,无法更改 。

(3.12之前)需要设置一个队列为惰性队列,只需要在声明队列时,指定x-queue-mode属性为lazy即可:

//声明Bean
@Bean
public Queue lazyQueue(){
    return QueueBuilder.durable("lazy.queue").lazy().build(); //创建惰性队列
}
//基于注解创建
@RabbitListener(queuesToDeclare = @Queue(
    name = "lazy.queue",
    durable = "true",
    arguments = @Argument(name = "x-queue-mode",value = "lazy")
))
public void listenLazyQueue(String msg){
    log.info("接收到lazy.queue的消息:{}",msg);
}

生产者发送非持久化消息直接存入Paged Out的消息

   @GetMapping("addQue")
   public String addQue(@Param("mess") String mess ){
      Message message = MessageBuilder.withBody(mess.getBytes(StandardCharsets.UTF_8)).setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT).build();
      for (int i = 0; i < 1000000; i++) {
         rabbitTemplate.convertAndSend("test","11",message  );
      }
      return "成功";
   }

消费者可靠性

消费者确认机制

当消费者处理消息结束后,影响Rabbitmq发送一个回执,告知自己消息处理状态。回执有三种可选值:

  • ack:成功处理消息,Rabbitmq从队列中删除消息
  • nack:消息处理失败,Rabbitmq需要再次投递消息
  • reject:消息处理失败并拒绝该消息,Rabbitmq从队列中删除消息

SpringAMQP已经实现了消息确认功能。并允许我们通过配置文件选择ACK处理方式,有三种方式:

  • none:不处理。即消息投递给消费者后立刻ack,消息会立刻从mq删除。非常不安全,不建议使用

  • manual:手动模式。需要自己在业务代码中调用api,发送ack或reject,存在业务侵入,但灵活

  • auto:自动模式。springAMQP利用AOP对我们的消息处理逻辑做了环绕增强,当业务正常执行时则自动返回

    ack。当业务出现异常,根据异常判断返回不同的结果:

    ​ ▫ 如果是业务异常,会自动返回nack

​ ▫ 如果是消息处理或校验异常,会自动返回reject

第一步、开启消费者确认其机制

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 #每次只能获取一条消息,处理完才能获取下一条消息
        acknowledge-mode: auto #none:关闭ack;manual:手动ack;auto:自动ack  

第二步、消费者业务模拟异常

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "twst"),
            exchange = @Exchange(name = "test", type = ExchangeTypes.DIRECT)))
    public void gettwst(Message message){
        System.out.println("twst:"+new String(message.getBody()));
         throw new RuntimeException("yihcang");
    }

消息失败处理策略

失败重试机制:

当消费者出现异常后,消息会不断requeue(重新入队)到队列,再重新发送给消费者,然后再次异常,再次requeue无限循环,导致mg的消息处理飙升,带来不必要的压力。

我们可以利用spring的retry机制,在消费者出现异常时利用本地重试,而不是无限制的requeue到MQ队列

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 #每次只能获取一条消息,处理完才能获取下一条消息
        acknowledge-mode: auto #none:关闭ack;manual:手动ack;auto:自动ack
        retry:
          enabled: true #开启消费者失败重试
          initial-interval: 1000ms #初始的失败等待时长为1秒
          multiplier: 1 #下次失败的等待时长倍数,下次等待时长 = multiplier * last-interval
          max-attempts: 3 #最大重试次数
          stateless: true #true无状态;false有状态。如果业务中包含事务,这里改为false

以抛出异常做测试

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "twst"),
        exchange = @Exchange(name = "test", type = ExchangeTypes.DIRECT)))
public void gettwst(Message message){
    System.out.println("twst:"+new String(message.getBody()));
    throw new RuntimeException("yihcang");
}

失败消息处理策略

在开启重试模式后,重试次数耗尽,如果消息依然失败,则需要有MessageRecoverer接口来处理,它包含三种不同的实现:

  • RejectAndDontRequeueRecoverer: 重试耗尽后,直接reject,丢弃消息。默认就是这种方
  • ImmediateRequeueMessageRecoverer:重试耗尽后,返回nack,消息重新入队。
  • RepublishMessageRecoverer: 重试耗尽后,将失败消息投递到指定的交换机。

第三中方式更为可靠

(1)首先,定义接收失败消息的交换机、队列及其绑定关系。

(2)然后,定义RepublishMessageRecoverer。

package com.cn.config;

import lombok.extern.slf4j.Slf4j;
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.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.retry.MessageRecoverer;
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DirectRabbitConfig {

    @Configuration
    @ConditionalOnProperty(prefix = "spring.rabbitmq.listener.simple.retry",name = "enabled",havingValue = "true") //开启了消费者失败重试机制才会生效
    public class ErrorConfiguration {

        @Bean
        public DirectExchange errorExchange(){
            return new DirectExchange("error.Exchange");
        }

        @Bean
        public Queue errorQueue(){
            return new Queue("error.queue");
        }

        @Bean
        public Binding errorBinding(Queue errorQueue,DirectExchange errorExchange){
            return BindingBuilder.bind(errorQueue).to(errorExchange).with("errorKey");
        }


        @Bean
        public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate){
            System.out.println("加载RepublishMessageRecoverer");
            return new RepublishMessageRecoverer(rabbitTemplate,"error.Exchange","errorKey");
        }
    }
}

消费者如何保证消息一定被消费?

  • 开启消费者确认机制为auto,由spring确认消息处理成功后返回ack,异常时返回nack。
  • 开启消费者失败重试机制,并设置MessageRecoverer,多次重试失败后将消息投递到异常交换机,交由人工处理

业务幂等性

幂等是一个数学概念,用函数表达式来描述是这样的: f(x)= f(f(X)。在程序开发中,则是指同一个业务,执行一次或多次对业务状态的影响是一致的。

方案一、是给每个消息都设置一个唯一id,利用id区分是否是重复消息

  • 每一条消息都生成一个唯一的id,与消息一起投递给消费者。
  • 消费者接收到消息后处理自己的业务,业务处理成功后将消息ID保存到数据库。(对业务有侵入)
  • 如果下次又收到相同消息,去数据库查询判断是否存在,存在则为重复消息放弃处理。
    @Bean
    public MessageConverter jacksonMessageConvertor() {
        //1.定义消息转换器
        Jackson2JsonMessageConverter jjmc = new Jackson2JsonMessageConverter();
        //2.配置自动创建消息id,用于识别不同消息,也可以在业务中基于ID判断是否是重复消息
        jjmc.setCreateMessageIds(true);
        return jjmc;
    }

方案二、是结合业务逻辑,基于业务本身做判断。以我们的业务为例:我们要在支付后修改订单状态为已支付,应该在修改订单状态前先查询订单状态,判断状态是否是未支付。只有未支付订单才需要修改,其它状态不做处理。

方案三、使用Token令牌,生成一个token存储在redis中,请求的时候携带这个token一起请求(Token 最好将其放到 Headers 中),后端需要对这个Token作为 Key在redis中进行校验,如果 Key存在就执行删除命令,然后正常执行后面的业务逻辑。如果不存在对应的 Key 就返回重复执行的错误信息,这样来保证幂等操作。

消息延迟

死信交换机

当一个队列中的消息满足下列情况之一时,就会成为死信:

  • 大消费者使用basic.reject 或者basic.nack 声明消费失败,并且消息的requeue参数设置为false
  • 消费是一个国企消息(达到了队列或者消息本身设置的过期时间),超时无人消费
  • 要投递的队列消息堆积满了,最早的消息可能成为死信

如果队列通过dead-letter-exchange属性指定了一个交换机,那么该队列中的死信就会投递到这个交换机中。这个交换机被称为死信交换机

队列指定死信交换机后发送一个延迟消息,经过一定时间后死信交换机会接收到消息。

延迟消息插件

RabbitMQ官方推出了一个插件,原生支持延迟消息功能。该插件的原理是设计了一种支持延迟消息功能的交换机,当消息投递到交换机后可以暂存一定时间,到期后在投递到队列。

需要声明delayed属性

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