消息队列中间件RabbitMQ基础——Spring AMQP、路由模型到可靠性

初识RabbitMQ

首先介绍MQ(Message Queue,消息队列),用于解决不同进程或线程的异步通信问题,该模块主要起三个作用:异步通信增加了并发性 ;同时缓存消息,可以实现流量的削峰填谷 ;作为消息中间件隔离进程干扰,降低耦合性 ,所以由于该设计理念,MQ天然适合在微服务架构中使用。

但要注意的是一些需要立即获取结果 才能继续执行的场景不适用MQ,比如支付场景,必须要获得支付结果才能继续进行,

RabbitMQ是消息队列的具体实现之一,此外还有ActiveMQ、RocketMQ和Kafka,其中RocketMQ是阿里的开源项目,使用也较多,Kafka吞吐量最高,而RabbitMQ凭借其较低延迟(微秒级,其余都是毫秒级),和可接受的吞吐量得到最为广泛的应用。

有关RabbitMQ的详细文档可见官网:https://www.rabbitmq.com

快速入门

安装

RabbitMQ支持Windows和Linux系统,安装方法多样,Linux可以部署较为方便的容器,Windows则提供Chocolatey管理器安装和安装程序两种方法。

简要介绍下这几种方法:

  1. Chocolatey自动安装。Chocolatey是Windows的包管理工具,类似Linux中的apt,python中的pip,软件先发布到仓库,用户直接通过该仓库拉取;
  2. 安装程序。RabbitMQ基于并发语言Erlang,先安装该语言环境,配置系统变量,再下载rabbitmq执行;
  3. docker容器。只需一行命令docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:4-management,即可拉取容器并指定端口,用户名等信息。

docker安装

生产环境下RabbitMQ多部署在Linux系统上,docker拉取运行也比较方便,但因为docker最近镜像源被封的很多,以前用的中科大现在不提供docker服务,阿里云等只对其服务器开放,所以只介绍下大致过程:

  1. 首先是安装docker:

    更新源

    sudo apt update

    安装docker

    sudo apt install docker.io docker-compose

    将当前用户加入 docker 组(无需 sudo 操作 Docker)

    sudo usermod -aG docker $USER

    刷新组权限(无需重启生效)

    newgrp docker

  2. 切换镜像源

    创建并打开配置文件

    sudo mkdir -p /etc/docker
    sudo vim /etc/docker/daemon.json

    镜像源添加到配置及文件

    {
    "registry-mirrors": [
    "https://docker.xuanyuan.me",
    "https://docker.mirrors.ustc.edu.cn"
    ]
    }

    重启守护进程与服务

    sudo systemctl daemon-reload
    sudo systemctl restart docker

  3. 拉取RabbitMQ

    拉取带管理界面的3版本

    docker pull rabbitmq:3-management

Windows安装

流程参考官网:https://www.rabbitmq.com/docs/install-windows

RabbitMQ基于Erlang语言,所以首先要安装语言环境,可以到官网https://www.rabbitmq.com/docs/which-erlang查找对应支持的Erlang版本,目前最新的RabbitMQ是4.2版本,对应最大匹配Erlang是27。

Erlang下载官网:https://www.erlang.org/downloads

RabbitMQ下载连接:rabbitmq-server-4.2

下载后的安装过程可以参考:Erlang与RabbitMQ的下载与安装

配置Erlang环境变量,系统变量中添加名为ERLANG_HOME值为安装路径的变量,path中添加%ERLANG_HOME%\bin,此时系统任意位置打开cmd,输入erl -version见到如下界面,说明Erlang环境配置成功。

rabbitmq正常安装后,在其sbin目录下打开cmd控制台,输入rabbitmq-plugins enable rabbitmq_management安装管理界面 ,访问http://localhost:15672,初始用户名和密码都是guest,可进入如下页面:

注意! 管理页面可能出现无法访问的情况。

参照这篇文章:15762无法访问解决

本质是用户管理授权问题,win+R并输入services.msc打开服务管理页面,找到rabbitmq,将登陆身份改为当前账户即可。

消息原理

MQ结构的主体是生产者Producer和消费者Consumer,分别负责通过发送和接收消息,有关RabbitMQ的大致工作原理如下:

  1. 用户将信息发送到交换机 Exchange,交换机结合自身类型,根据路由标签Routing key与队列匹配规则Binding key决定要将消息转发到哪些队列;
  2. 队列 Queue是消息暂存的容器(先进先出),默认状况下多消费者轮询读取消,费者确认读取后消息消失;
  3. 虚拟主机 Virtual Host类似网络中码分复用技术,将消息队列隔离划分成多个区域,可充分利用其吞吐量,多个业务可共用MQ,数据通过虚拟主机分隔保证安全。

控制台使用

新增队列

新增交换机

绑定队列并发送消息

随后在队列中可获取信息

此外还可以在管理员界面新增用户与虚拟主机

rabbitmq默认管理端口15672默认消息监听端口5672 ,docker可通过运行指定修改端口等配置信息,Windows中可到C:\Users\用户名\AppData\Roaming\RabbitMQ下新建rabbitmq.conf,使用listeners.tcp.default = 15763 management.tcp.port = 15672修改端口等信息,重启服务生效。

Java客户端------Spring AMQP

AMQP是Advanced Message Queuing Protocol(高级消息队列协议),是一套跨语言、跨平台的通信协议标准;RabbitMQ是AMQP的具体实现,Spring AMQP对AMQP协议进行了抽象封装,并对RabbitMQ进行了适配,提供了更简单的调用方法。

官网给出的RabbitMQ的Java发送方法:

使用Spring AMQP 只需要rabbitTemplate.convertAndSend(队列名,信息)

补充:AMQP就像python生成的diffusers库,整合各类生成模型,统一标准,为不同模型进行了简化适配。
引入依赖

复制代码
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-amqp</artifactId>
 </dependency>

配置连接参数

yml 复制代码
spring:
  rabbitmq:
    host: localhost
    port: 5672
    virtual-host: java
    username: user1
    password: 123

RabbitTemplate核心类
RabbitTemplate是 Spring AMQP 提供的一个核心工具类,用于简化RabbitMQ的操作,如发送、接收消息等。

简单使用实例如下:

java 复制代码
@Autowired
private RabbitTemplate rabbitTemplate;

@Test
public void rabbitTemplateTest() {
    String message = "Hello World!";
    String queueName = "queue1";
    String exchangeName = "exchange1";
    // 向队列发送消息
    rabbitTemplate.convertAndSend(queueName,message);
    // 向交换机发送消息
    // rabbitTemplate.convertAndSend(exchangeName,message);
	// 主动拉取消息
    System.out.println("收到消息:"+rabbitTemplate.receiveAndConvert(queueName));
}
// 被动消费,后台监听,消息自动传到message参数中,message可以是任意类型
@RabbitListener(queues = "queue1")
String getMessage(String message) {
    System.out.println("消费者收到消息"+message);
    return message;
}

消费模型------工作队列模型

工作队列模型是实际生产环境中最常用的一种消费模型,即多个消费者共享同一个队列,竞争消费信息,形成如下图的结构:

这种方式允许多个消费者并行处理消息,避免消息拥堵,提高并发性,但在消息原理中我们提到,队列的默认消息分发逻辑是轮询,也就是说消息会平均分配,当消费者处理能力不同时会导致资源浪费。

为了充分利用资源,可以采用预分配或应答策略,让消费者处理完数据后再从队列中获取消息。

预分配prefetch

Spring AMQP根据prefetch(预取数)限制一个信道下的单个消费者手中已推送但未确认(ACK)的消息最大数量(默认 prefetch=250),将该值设置为1后,消费者必须处理完当前消息并完成 ACK 确认,才能获取新消息,实现按执行效率分发,而非轮询分配。

yml 复制代码
spring:
  rabbitmq:
    host: localhost
    port: 5672
    virtual-host: java
    username: user1
    password: 123
    listener:
      simple:
        prefetch: 1

路由模型

消费模型决定消息消费的策略,路由模型则决定生产交换机分发到队列的方法,二者任意组合成不同维度规则,共同构成 RabbitMQ 消息流转的完整逻辑。

根据分发策略不同,路由模型主要分为以下三种:

Fanout广播模型

Fanout采用广播策略,即消息会转发到每一个与之绑定的队列,适用于多个需要处理同样数据的微服务。

Spring AMQP提供声明交换机、队列及绑定关系的代码,不同模式只需创建不同的交换机实例即可,使用示例如下:

java 复制代码
// 生产者
// 消息转发到交换机,路由键设为空
rabbitTemplate.convertAndSend(exchangeName, "", message);

// 消费者
// 声明 Fanout Exchange
@Bean
public FanoutExchange fanoutExchange() {
    return new FanoutExchange("demo.fanout");
}

// 创建队列
@Bean
public Queue queue1() {
    return new Queue("queue1");
}
// 创建交换机
@Bean
public FanoutExchange exchange1() {
    return new FanoutExchange("exchange1");
}

@Bean
public Binding binding1(Queue queue1, FanoutExchange exchange1) {
    return BindingBuilder.bind(queue1).to(exchange1);
}

@RabbitListener(queues = "queue1")
void getMessage(String message) {
    System.out.println("消费者收到消息:"+message);
}

Direct定向模型

定向模型可以将消息映射到指定的队列中,通过消息的Routing Key与队列的Binding Key匹配实现。

即消息发送时绑定routing key,交换机将其与队列映射规则的binding key进行比较,向符合规则的队列转发,可以实现消息的定向发布,示例图如下:

代码实现示例如下:

java 复制代码
// 生产者只需绑定routing key,交换机自动完成后续转发
rabbitTemplate.convertAndSend(exchangeName, "red", message);

// 消费者可使用DirectExchange类型绑定队列,并确定匹配规则
@Bean
public DirectExchange exchange1() {
    return new DirectExchange("exchange1");
}
@Bean
public Binding binding1(Queue queue1, DirectExchange exchange1) {
    return BindingBuilder.bind(queue1).to(exchange1).with("blue");
}

// 也可以使用注解一次性配置完成
@RabbitListener(bindings = @QueueBinding(
       value = @Queue(name = "queue1"),
       exchange=@Exchange(name = "exchange1",type = ExchangeTypes.DIRECT),
       key={"blue"}
))
String getMessage(String message) {
   System.out.println("消费者收到消息"+message);
   return message;
} 

注解一次性配置中,@QueueBinding用于声明绑定关系,value声明队列,exchange声明交换机,并设置映射规则Binding Key

Topic话题模型

话题模型可以认为是更灵活的定向模型,Topic支持多个单词列表的Routing Key,单词通过.分割,匹配规则可以包括通配符,以实现特定话题的转发。

该模型下的消息发布示意图如下:

通配符中#表示0或多个单词,*表示一个单词,所以#.#可以表示接收任意类型的消息。

生产者只需在Routing Key中设置以.相连的多个单词,消费者更换路由模式,及Binding Key即可接收信息,使用示例如下:

java 复制代码
// 生产者示例
rabbitTemplate.convertAndSend(exchangeName, "China.news", message);

// 消费者示例
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "queue1"),
        exchange=@Exchange(name = "exchange1",type = ExchangeTypes.TOPIC),
        key={"China.#"}
))
String getMessage(String message) {
    System.out.println("消费者收到消息"+message);
    return message;
}

消息转换器

消息队列中的消息可以是任意类型,包括对象引用,但Spring AMQP底层默认使用JDK序列化,会导致引用类型可读性差,需要将序列化对象更换为更适合的Jackson。

引入jackson-databind依赖后,通过将消息转换器添加到容器中,覆盖原有方案,实现消息转换器的更换。

java 复制代码
@Bean
public MessageConverter messageConverter(){
    return new Jackson2JsonMessageConverter();
}

有关该部分的详细介绍及代码,可见:基于 Spring AMQP 实现简单、工作队列模型,发布订阅模型

消息可靠性

RabbitMQ作为一个中间件,实现消息解耦的同时也带来了额外的运维开销,最主要的就是消息传递可靠性的保证问题。

从错误形式来说,可能有两种场景:

  1. 消息未送达。因为网络波动或服务宕机,导致消息在发送的一个环节出错,而最终未被接收。这种错误可以参考网络中的确认重传机制解决。
  2. 消息重复。确认重传带来的问题就是消息重复,单纯网络拥堵也可能导致时间窗口内未收到确认消息,这导致队列中有两条重复消息。

中间件连接生产者和消费者,从错误产生位置来说,可能出现在三个阶段:

  1. 生产者可靠性。生产者的消息是否正确到达MQ,比如网络丢包,MQ宕机等情况。
  2. MQ可靠性 。MQ接收到的消息是否被正确转发,如路由键Routing Key错误配置。
  3. 消费者可靠性。消费者是否取到合规的消息,如消息格式不符,消费者自身程序问题等。

RabbitMQ主要针对这三个环节分别设计了可靠性方案。

生产者可靠性

生产者只负责向交换机发送数据,所以只需解决消息未送达问题,导致该问题的原因可能有两个:

  1. RabbitMQ连接失败
  2. 网络问题

RabbitMQ分别用重试机制确认机制解决该问题。

重试 是当RabbitMQ连接失败时自动重连的策略,可通过配置文件application.yml实现,文件中指定超时时间、失败后的初始等待时间、失败后的等待时间倍数和最大重连次数,示例如下:

yml 复制代码
spring:
  rabbitmq:
    connection-timeout: 1s # 超时连接时间
    template:
      retry:
        enabled: true # 开启重试
        initial-interval: 1000ms # 初始等待时间
        multiplier: 1 # 失败后下次等待时间的倍数
        max-attempts: 3 # 最大重试次数

要注意的是尝试时间是超时时间+初始等待时间*(n*等待时间倍数),如第一次尝试时间为1s+1000ms=2s

另外该方案只能解决网络原因导致的连接失败,同时采用阻塞式重试,高性能要求的场景下可以关闭该机制。

确认 机制是MQ收到消息后返回处理状态给生产者的方法,告知其消息状态,返回类型分为ReturnConfirm两种,具体场景如下:

  1. 消息到达MQ,但路由失败,Return异常原因,Confirm返回ack(通常是路由与队列的映射关系出错)
  2. 消息到达MQ并成功入队,返回ack
  3. 持久化消息入队,并持久化后,返回ack
  4. 其他情景都返回nack

其中acknack属于ConfirmReturnnack都表示消息错误,只有ack单独出现才表示消息成功。

开启配置如下:

yml 复制代码
spring:
  rabbitmq:
    publisher-confirm-type: correlated # 开启confirm机制,simple同步等待,correlated异步回调
    publisher-returns: true # 开启return机制,返回路由失败信息

代码层面每个rabbitTemplate只能配置一个ReturnsCallback用于处理Return信息,示例如下:

java 复制代码
void testConnection() {
   // 开启mandatory模式,将路由信息返回给生产者
   rabbitTemplate.setMandatory(true);

   // ReturnsCallback,用Lambda简化
   rabbitTemplate.setReturnsCallback(returnedMessage -> {
       // 匿名内部类获取关键信息,省略类型配置,可只写方法体
       String exchange = returnedMessage.getExchange();
       String routingKey = returnedMessage.getRoutingKey();
       Message message = returnedMessage.getMessage();
       int replyCode = returnedMessage.getReplyCode();
       String replyText = returnedMessage.getReplyText(); // 补充获取错误描述

       // 打印日志 + 记录失败消息
       System.err.printf("消息路由失败!交换机:%s,路由键:%s,响应码:%d,错误信息:%s,消息体:%s%n",
               exchange, routingKey, replyCode, replyText, new String(message.getBody()));
   });
}

ConfirmCallBack回调因为消息逻辑不同,需要单独指定。具体流程为:在发送信息的convertAndSend方法中额外指定一个CorrelationData示例,用于处理回调信息。

具体实例如下:

java 复制代码
void Confirm(){
   // 消息关联元数据对象,绑定消息与确认结果
   // UUID.randomUUID().toString(),128位数字随机,工程上几乎保证唯一
   CorrelationData correlationData=new CorrelationData(UUID.randomUUID().toString());
   // 获取异步结果容器,增加回调方法
   correlationData.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {
       // 回调接口,监听执行结果
       @Override
       public void onFailure(Throwable ex) {
           // onFailure代表获取信息失败,而不代表获取到失败信息
           System.out.println("处理结果异常:"+ex.getMessage());
       }
       @Override
       public void onSuccess(CorrelationData.Confirm result) {
           if (result.isAck()){
               System.out.println("消息处理成功");
           }
           else {
               System.out.println("消息处理失败");
           }
       }
   });

   rabbitTemplate.convertAndSend("exchange1","red","message",correlationData);
}

复杂的回调结构让我想起动态代理

实现一个增强方法类,通过被代理类、其接口以及增强类的字节码构建代理类

实现增强被代理类的方法,现在看起来好像也不涉及回调,只是几个反射的组合,不知道为什么这里想起动态代理,可能就是我当时觉得最复杂的结构了吧

生产者可靠性小总结

确认和重试都会影响性能,网络能正常连通的情况下内部路由不会出问题(除非配置错误),而如果是网络有问题则通常不是重试几次能解决的,特殊场景下开启,也推荐针对nack作有限重试即可。

MQ可靠性

MQ作为消息的暂存容器,其最大的不可靠因素就是存储层故障。如部署消息队列的服务器关机重启,解决这一问题的首要方案就是持久化

MQ内部核心组件有两个:交换机和队列,Spring默认开启持久化,控制台内可通过Durable开启。

另一个增强队列可靠性的方案就是惰性队列Lazy Queue,原有方案当内存到水位线时会触发page out操作,即将先入队的消息换出到磁盘中。目的是压缩内存使用,让后续消息能正常入队,但该过程极大影响性能(会导致消息发送时出现剧烈波动,新RabbitMQ默认惰性队列,无法复现),基于该问题提出了惰性队列。

page out是将先入队且未被消费的消息换出到磁盘中(我还是觉得应该换出后入队的,没找到相关文档介绍),队列中只保留其索引,读到该消息再从磁盘中取出来。

惰性队列是将消息先持久化到磁盘,读取时再从磁盘恢复到内存中,这种方式的好处是:

  • 持久化,先写入磁盘,保证消息可靠性,同时优化了写入操作,效率高于传统持久化。
  • 避免阻塞,按需加载消息,减少内存占用,避免内存满触发的阻塞。
  • 动态缓存解决读写瓶颈。当消费者处理速度较慢时完全按需加载,处理速度较快时会默认预加载2048条消息到内存,避免磁盘读取的瓶颈影响消费者的处理速度。

开启惰性队列的方法如下:

java 复制代码
@Bean
public Queue getQueue() {
    // 创建持久化的惰性队列
    return QueueBuilder.durable("queue1").lazy().build();
}

// 注解一次性配置
@RabbitListener(bindings = @QueueBinding(
       value = @Queue(name="queue1",arguments = @Argument(name="x-queue-mode",value = "lazy")),
       exchange = @Exchange(name = "exchange1",type = ExchangeTypes.TOPIC),
       key = {"blue"}
))
String getMessage(String message) {
   System.out.println("消费者收到消息"+message);
   return message;
}

持久化和惰性队列都是Spring和RabbitMQ新版本的默认配置,所以MQ可靠性这一块也不用我们操心。

消费者可靠性

消费者端也使用确认机制向MQ发送回执,以告知消息的处理状态,便于MQ删除或重传消息,回执有以下三种:

  • ack,成功处理消息,MQ可删除队列中的消息
  • nack,处理消息失败,MQ应再次投递消息
  • reject,消息处理失败并拒绝,MQ应删除队列中的消息(通常是消息格式问题)

由于消息回执的场景比较固定,Spring AMQP为我们实现了自动确认,有以下三种配置模式:

  • none,不处理回执,消费者收到消息自动发送ack,MQ从队列中删除消息
  • manual,开发者手动发送ack,更灵活
  • auto,自动确认,Spring AOP环绕增强,业务正常时返回ack,异常返回nack,消息转换异常时返回reject

消费者端配置方法如下:

yml 复制代码
spring:
    listener:
      simple:
        acknowledge-mode: auto

回执为nack时MQ会重新投递,可当消费者自身问题无法正确处理时,MQ默认无限重入队会增加系统压力,所以失败重试机制也应作配置。

retry本地重试 :避免无限重新投递。当消费者执行错误时先不返回nack,而是使用本地重试(再次调用消费方法),重试次数大于配置值时直接返回reject拒绝,配置方式如下:

yml 复制代码
spring:
    listener:
      direct:
        retry:
          enabled: true # 开启本地重试
          initial-interval: 1000ms # 初始等待时长
          multiplier: 1 # 失败的等待时长倍数
          max-attempts: 3 # 最大重试次数
          stateless: true # 无状态,有事务要求设置为有状态

重试耗尽的消息处理策略(MessageRecover接口):本地重试方法默认将到达重试上限的消息直接丢弃,对于可靠性要求较高的场景不适用,应该单独设置策略处理这类消息。

处理策略通过MessageRecover接口实现,具体有三种不同的实现策略:

  • RejectAndDontRequeueRecoverer:默认策略,重试次数耗尽直接丢弃消息
  • ImmediateRequeueMessageRecoverer:重试次数耗尽返回nack,消息重新入队,只是拖延了消息重试,并未解决问题
  • RepublishMessageRecoverer:重试耗尽,将失败的消息发送到指定交换机(死信交换机),这种方式最优雅,真正的问题应该人工介入解决

创建消息处理策略的示例如下:

java 复制代码
@Bean
public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate) {
// 绑定交换机和routing key
   return new RepublishMessageRecoverer(rabbitTemplate,"error.exchange","error");
}

这是RabbitMQ本地重试机制的兜底,将所有死信发送到指定交换机,与rabbitmq死信交换机不同。

rabbitmq死信交换机配置于队列,通过指定参数Dead letter exchange=exchange的方式实现死信转发。

业务幂等性

幂等性原本是数学定义,f(x)=f(f(x)),指重复运算仍等于其自身的方法,如|x|绝对值函数,在计算机中指同一个业务执行一次或多次对业务的影响一致,有些业务天然具有幂等性,如查询、删除,重复操作不会造成状态影响;而有些业务则影响极大,如下单、退款。

虽然我们从三个层面尽可能保证了消息的可靠性,业务幂等性 则是该保障体系下最后的兜底手段

幂等性主要解决同一消息被多次处理导致业务出错的问题,第一个思路是把消息绑定唯一ID,处理消息时查库对比,已有记录直接丢弃。

可使用AMQP消息转换器的MessageID功能,以Jackson转换器为例,示例代码如下:

java 复制代码
@Bean
public MessageConverter messageConverter(){
    // 定义消息转换器
    Jackson2JsonMessageConverter jjmc = new Jackson2JsonMessageConverter();
    // 配置自动创建消息id
    jjmc.setCreateMessageIds(true);
    return jjmc;
}

幂等性的其他保证方法可见:业务幂等性设计的六种方案

延迟队列

现有队列是生产后消费者立即取出,取出时间取决于消费者的处理能力,有些场景下我们可能希望消费者在指定间隔后再处理,如:

  1. 订单创建30分钟后未支付自动删除;
  2. 收货三天自动确认;
  3. 发车半小时前提醒乘客

数据量较大的场景下定时任务轮询会给数据库带来极大压力,此时就需要用到延迟队列。

死信交换机

消费者可靠性中我们提到,异常的消息通常设置转发到特定交换机,来让人工介入处理,这个特定交换机我们称为死信交换机。

死信,也就是需要人工处理的消息有以下三类:

  • 消费者返回reject或nack声明消费失败的消息,并且参数requeue设置为false;
  • 超时无人消费的过期消息
  • 队列已满时,早期消息可能成为死信

我们同样可以利用该机制实现延迟队列,给消息设置过期时间,发送到无消费者监听的队列,绑定死信交换机后,超时自动转发到死信交换机。

死信交换机实现延迟队列结构如下:

这种方式能实现需求,复用性也强,但从开发者角度来说,除了死信交换机和队列以外,还要额外部署一个暂存消息的队列,配置复杂,所以生产中通常使用延迟插件。

延迟插件

插件发布地址:https://www.rabbitmq.com/community-plugins

搜索要下载的插件delay后点击release可见如下页面:

文件复制到安装目录的\plugins下,Windows切换到sbin目录打开cmd,Linux直接执行rabbitmq-plugins enable rabbitmq_delayed_message_exchange启用插件,出现如下界面说明启用成功。

创建延迟交换机的方法如下:

java 复制代码
@Bean
public DirectExchange exchange1() {
    return ExchangeBuilder.directExchange("delay.direct").delayed() // 设置延迟属性
            .build();
}

@RabbitListener(bindings=@QueueBinding(
        value = @Queue(name="delay.queue"),
        exchange = @Exchange(name="delay.direct",delayed = "true"),
        key = "delay"
))
void getDelayMessage(String message) {
    System.out.println("获取到延迟消息"+message);
}

发送消息需要配置延迟时间,通过MessagePostProcessor类实现,实际项目中可将该类独立出来,发送延迟消息示例代码如下:

java 复制代码
String message="delay message";
// 向队列发送消息
String exchange="delay.direct";
rabbitTemplate.convertAndSend(exchange, "delay", message, new MessagePostProcessor() {
   @Override
   public Message postProcessMessage(Message message) throws AmqpException {
       // 设置超时时间
       message.getMessageProperties().setDelay(10000);
       return message;
   }
});

总结

花了四天时间补了个后端技术栈,用下面一张图就能概括了。

相比远程调用,消息队列的意义可以简要概括为解耦与缓存

而由于引入了中间件,该中间件的可用性又需要分三个层次保证:
生产者端 几乎不会出现问题,重试重连解决不了基本就没办法了,通常不用管;
MQ 通过持久化来保证数据可用,现有持久化及队列方案都是默认的,也不用配置;
消费者端 Spring也提供了自动应答机制,唯一要处理的就是指定重试上限消息转发的死信交换机,让人工介入。

相关推荐
哈哈哈笑什么19 小时前
企业级高并发分布式SpringCloud系统下,订单动态超时自动取消(最终成熟方案),使用spring-cloud-starter-stream-rabbit
分布式·spring cloud·rabbitmq
一只懒鱼a21 小时前
docker搭建rabbit集群
docker·容器·rabbitmq
苦学编程的谢1 天前
RabbitMQ_6_高级特性(3)
分布式·rabbitmq
梁正雄1 天前
linux服务-RabbitMQ 原理与安装
linux·rabbitmq·ruby
醉风塘2 天前
RabbitMQ状态与配置深度解读:构建高性能消息中间件的关键指标
分布式·rabbitmq
sugar椰子皮2 天前
【爬虫框架-6】中间件的另一种写法实现
爬虫·中间件
添加shujuqudong1如果未回复2 天前
无刷直流电机BLDC转速电流双闭环调速系统Matlab Simulink仿真
rabbitmq
better_liang2 天前
每日Java面试场景题知识点之-RabbitMQ
java·消息队列·rabbitmq·面试题·异步通信·企业级开发·系统解耦
天海行者2 天前
多数据源 + ShardingSphere 分库分表 + 读写分离 实现方案
中间件·shardingsphere·dynamic