目录
什么是消息队列
进行大量的远程调用时,传统的Http方式容易造成阻塞,所以引入了消息队列的概念,即让消息排队,按照队列进行消费。
它能够将发送方发送的信息放入队列中,当新的消息入队时,会通知接收方进行处理,一般消息发送方称为生产者,接收方称为消费者。
这样所有的请求,都可以丢到消息队列中,再由消费者取出,不再是直接连接消费者的形式了,而是加了一个中间件,这是一种很好的解耦方案。并且在高并发的情况下,由于消费者能力有限,消息队列也能起到一个削峰填谷的作用,堆积一部分的请求,再由消费者来慢慢处理,而不会像直接调用那样请求蜂拥而至。
消息队列组件
ActiveMQ、RabbitMQ、RocketMQ、Kafka是目前主流的消息队列组件。它们的特点如下:
综合以上因素,这里选择RabbitMQ作为消息队列组件进行演示。原因如下:
- 吞吐量虽然不高,但中小型项目完全足够。
- 时效性强。延迟达到微妙级别,较同类产品延迟最低。
- 可用性强。提供了完善的高可用实现机制。
- 可靠性强。基本不会丢包。
- 并发能力强。基于Erlang开发,提供了丰富的交换机功能,性能好、延时低。
- 开源免费。没有使用成本,且社区活跃度高,产品稳定。
消息队列组件的功能和实现是大同小异的,学习好一个组件就可以做到一通百通,而且还有Spring Cloud Stream这样的消息队列连接组件,可以屏蔽各消息队列组件的差异性,使用统一的标准调用消息队列组件。
所以,想要掌握消息队列,从RabbitMQ入手是一个比较好的选择。
RabbitMQ
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。
Rabbit科技有限公司开发了RabbitMQ,并提供对其的支持。
- 起初,Rabbit科技是LSHIFT和CohesiveFT在2007年成立的合资企业。
- 2010年4月被VMware旗下的SpringSource收购。
- RabbitMQ在2013年5月成为GoPivotal的一部分。
其特点如下:
- 拥有数万计的用户,是最受欢迎的开源消息队列之一,从T-Mobile到Runtastic,RabbitMQ在全球范围内用于小型初创企业和大型企业。
- 轻量级,易于在本地和云端部署,它支持多种消息协议。
- 可以部署在分布式和联合配置中,以满足大规模、高可用性要求。
- 在许多操作系统和云环境中运行,并为大多数流行语言提供了广泛的开发者工具。
运行过程:
- 生产者(Publisher):生产消息的终端。
- 信道(Channel):服务端和客户端连接都会使用一个Channel,再通过Channel去访问到RabbitMQ服务器。这里的通信协议不是http,而是amqp协议。
- 虚拟主机(Virtual Host):类似于环境隔离,不同环境都可以单独配置一个Virtual Host,每个Virtual Host可以包含很多个Exchange和Queue,每个Virtual Host相互之间不影响。
- 交换机(Exchange):根据请求,转发给相应的消息队列,每个队列都可以绑定到Exchange上,这样Exchange就可以将数据转发给队列了,可以存在很多个,不同的Exchange类型可以用于实现不同消息的模式。
- 消息队列(Queue):消息队列本体,生产者所有的消息都存放在消息队列中,等待消费者取出。
- 消费者(Consumer):消费消息的终端。
安装
推荐使用Docker进行安装。
- 执行如下命令。
docker run -d --restart always --name rabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883 rabbitmq:management
参数解释:
- `-d`:设置程序后台运行。
- `--restart always`:设置服务自启动。
- `--name`:指定运行后的容器名称。
- `-e`:设置环境。
这里主要设置账号密码为admin。
默认账号密码为guest ,只能在 localhost访问,由于需要外网访问,所以创建admin用户进行登录。
- `-p`:设置公网IP地址的端口号对应容器内部的端口号。
- `rabbitmq:management`:安装可视化管理组件。
-
访问可视化界面。
访问地址:服务器IP:15672
这里以本地访问为例:
-
使用设置的账号密码登录。
这里使用admin/admin进行登录,登录后的界面为:
-
环境
Ubuntu 22.04
RabbitMQ 3.11.10
Erlang 25.3
直连模式-可视化界面
这里先演示最简单的模型:直连模式。其结构图为:
一个生产者 -> 消息队列 -> 一个消费者
生产者只需要将数据丢进消息队列,而消费者只需要将数据从消息队列中取出,这样就实现了生产者和消费者的消息交互。
-
创建一个新的实验环境,即新建一个Virtual Host。
-
添加新的虚拟主机之后,我们可以看到,当前admin用户的主机访问权限中新增了刚刚添加的环境。
-
查看交换机。
交换机列表中自动新增了刚刚创建好的虚拟主机相关的预设交换机,一共7个。
这里首先介绍一下前面两个
direct
类型的交换机,一个是(AMQP default)
还有一个是amq.direct
,它们都是直连模式的交换机。-
单击
(AMQP default)
进入详情。(AMQP default)
是所有虚拟主机都会自带的一个默认交换机。-
此交换机默认绑定到所有的消息队列。
如果是通过默认交换机发送消息,会根据消息的
routingKey
(发消息时指定)决定发送给哪个同名的消息队列,同时也不能显式地将消息队列绑定或解绑到此交换机。 -
此交换机不可删除。
可以看到,详细信息中,特征(Features)项为:
durable:true
,表明当前交换机特性是持久化的,也就是说就算机器重启,此交换机也会保留;如果不是持久化,那么一旦重启就会消失。在列表中看到
D
的字样,就表示此交换机是持久化的。所有自动生成的交换机都是持久化的。
-
单击
amq.direct
进入详情。这个交换机和默认交换机类型一致,并且也是持久化的。
但是可以看到它是具有绑定关系的,如果没有指定的消息队列绑定到此交换机上,那么这个交换机无法正常将信息存放到指定的消息队列中,也是根据
routingKey
寻找消息队列(可以自定义)。
-
-
查看消息队列。目前没有消息队列,所以需要创建一个。
- Virtual host:虚拟主机。这里选择自建的,在这个虚拟主机下创建此消息队列。
- Type:类型。选择
Classic
,也就是经典类型。 - Name:名称。可以随便取,这里取test。
- Durability:持久化。这里选择Durable,即持久的。
- Auto delete:自动删除。这里选择
No
,如果选Yes
,即代表需要至少有一个消费者连接到这个队列,一旦所有与这个队列连接的消费者都断开时,就会自动删除此队列。 - Arguments:参数。暂时不用设置。
-
点击创建的消息队列名称,可查看详情。
详细信息中包括队列的当前负载状态、属性、消息队列占用的内存,消息数量等。
从绑定信息可以发现,该队列默认绑定了交换机,就是前面介绍的
(AMQP default)
默认交换机。现在需要将此消息队列绑定到
amq.direct
,这样就可以通过此交换机向此消息队列发送消息了: -
回到交换机。这里也显示了与队列的绑定关系。
向该消息队列中发送一条消息:
-
回到队列。
可以看到已经有一条消息了:
-
获取消息。
选择Get messages,可以获取消息:
-
Ack Mode:接收消息的模式。有4种,分别为:
Nack message requeue true
:获取到消息的内容;不会去消费消息。Automatic ack
:获取到消息的内容;会去消费消息。Reject requeue true
:拒绝获取消息;消息重新入队。-
Reject requeue false
:拒绝获取消息;消息不重新入队,将会被删除。这里使用默认的即第1种就可以了,这样只会查看消息,但是不会取出,消息依然存在于消息队列中。
- Encoding:编码格式。使用默认的就可以。
-
Messages:要生效的操作数量。选择1就行。
消息已经成功读取到。
-
-
除了在交换机发送消息给消息队列之外,也可以直接在消息队列这里发。
- Delivery mode:推送方式。
- Non-persistent:不持久化。如果服务器重启,此消息删除。
- Persistent:持久化。如果服务器重启,此消息依然存在。
- Headers:头部信息。
- Properties:属性。
- Payload:载荷的消息。
- Delivery mode:推送方式。
-
如果不需要再使用此消息队列了,可以手动对其进行删除或是清空。
- 环境
- Ubuntu 22.04
- RabbitMQ 3.11.10
- Erlang 25.3
直连模式-Java操作
使用Java原生的方式使用RabbitMQ现在已经较少,但这是基础,还是有必要了解的。
1.引入依赖。
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.16.0</version>
</dependency>
2.实现生产者。
// 使用ConnectionFactory创建连接
ConnectionFactory factory = new ConnectionFactory();
// 设置连接信息
factory.setHost("127.0.0.1");
// 注意这里写5672,是amqp协议端口
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("admin");
factory.setVirtualHost("/test");
// 创建Connection
Connection connection = factory.newConnection();
// 通过Connection创建新的Channel
Channel channel = connection.createChannel();
try (connection; channel) {
// 声明队列,如果此队列不存在,会自动创建
channel.queueDeclare("test_java", false, false, false, null);
// 将队列绑定到交换机
channel.queueBind("test_java", "amq.direct", "test_java_key");
// 转换为byte[],发布消息
channel.basicPublish("amq.direct", "test_java_key", null, "Hello World!".getBytes());
}
-
queueDeclare
方法参数如下:-
queue:队列的名称(默认创建后routingKey和队列名称一致)
-
durable:是否持久化。
-
exclusive:是否排他。
如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。
排他队列是基于Connection可见,同一个Connection的不同Channel是可以同时访问同一个连接创建的排他队列。
如果一个Connection已经声明了一个排他队列,其他的Connection是不允许建立同名的排他队列的。
即使该队列是持久化的,一旦Connection关闭或者客户端退出,该排他队列都会自动被删除。
-
autoDelete:是否自动删除。
-
arguments:设置队列的其他一些参数,这里暂时不需要。
-
-
queueBind
方法参数如下:- queue:需要绑定的队列名称。
- exchange:需要绑定的交换机名称。
- routingKey:路由标识。
-
basicPublish
方法参数如下:- exchange:对应的Exchange名称,这里就使用第二个直连交换机。
- routingKey:这里填写绑定时指定的routingKey,其实和之前在管理页面操作一样。
- props:其他的配置。
- body:消息本体。
执行完成后,可以在管理页面中看到刚刚创建好的消息队列:
并且此消息队列已经成功与
amq.direct
交换机进行绑定:
3.实现消费者。
// 使用ConnectionFactory创建连接
ConnectionFactory factory = new ConnectionFactory();
// 设置连接信息
factory.setHost("127.0.0.1");
// 注意这里写5672,是amqp协议端口
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("admin");
factory.setVirtualHost("/test");
// 这里不使用try-with-resource,因为消费者是一直等待新的消息到来,然后按照设定的逻辑进行处理,所以这里不能在定义完成之后就关闭连接
// 创建Connection
Connection connection = factory.newConnection();
// 通过Connection创建新的Channel
Channel channel = connection.createChannel();
// 创建一个基本的消费者
channel.basicConsume("test_java", false, (s, delivery) -> {
System.out.println(new String(delivery.getBody()));
/*
确认应答
参数 1:当前的消息标签
参数 2:是否批量处理消息队列中所有的消息,如果为false表示只处理当前消息
*/
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
/*
拒绝应答
参数 1:当前的消息标签
参数 2:倍数
参数 3:是否将当前消息放回队列,如果为false,那么消息就会被丢弃
*/
// channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false);
/*
拒绝应答
参数 1:当前的消息标签
参数 2:是否将当前消息放回队列,如果为false,那么消息就会被丢弃
*/
// channel.basicReject(delivery.getEnvelope().getDeliveryTag(), false);
}, s -> {
});
basicConsume
方法参数如下:
-
queue:消息队列名称,直接指定。
-
autoAck:自动应答。
消费者从消息队列取出数据后,需要跟服务器进行确认应答,当服务器收到确认后,会自动将消息删除,如果开启自动应答,那么消息发出后会直接删除。
-
deliver:消息接收后的函数回调。
可以在回调中对消息进行处理,处理完成后,需要给服务器确认应答。
-
cancel:当消费者取消订阅时进行的函数回调,这里暂时用不到。
执行上述代码后,控制台输出:Hello World!
,说明消息消费成功。
直连模式-Spring Boot操作
Spring Boot操作
Spring Boot集成RabbitMQ是现在主流的操作RabbitMQ的方式。
官方文档:https://docs.spring.io/spring-amqp/docs/current/reference/html/
1.引入依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.添加配置。
spring:
rabbitmq:
addresses: 127.0.0.1
username: admin
password: admin
virtual-host: /test
3.配置类。
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* RabbitMQ配置类
*/
@Configuration
public class RabbitMqConfig {
/**
* 定义交换机,可以很多个
* @return 交换机对象
*/
@Bean("directExchange")
public Exchange exchange(){
return ExchangeBuilder.directExchange("amq.direct").build();
}
/**
* 定义消息队列
* @return 消息队列对象
*/
@Bean("testQueue")
public Queue queue(){
return QueueBuilder
// 非持久化类型
.nonDurable("test_springboot")
.build();
}
/**
* 定义绑定关系
* @return 绑定关系
*/
@Bean
public Binding binding(@Qualifier("directExchange") Exchange exchange,
@Qualifier("testQueue") Queue queue){
// 将定义的交换机和队列进行绑定
return BindingBuilder
// 绑定队列
.bind(queue)
// 到交换机
.to(exchange)
// 使用自定义的routingKey
.with("test_springboot_key")
// 不设置参数
.noargs();
}
}
普通消费
1.实现生产者。
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RabbitMqSpringBootTests {
/**
* RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
*/
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 生产者
*/
@Test
void producer() {
/*
发送消息
参数 1:指定交换机。
参数 2:指定路由标识。
参数 3:消息内容。
*/
rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", "Hello World!");
}
}
运行代码后,查看可视化界面,可以看到创建了一个新的队列:
绑定关系也已经建立:
2.实现消费者。
消费者实际上就是一直等待消息然后进行处理的角色,这里只需要创建一个监听器就行了,它会一直等待消息到来然后再进行处理:
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 直连队列监听器
* @author CodeSail
*/
@Component
public class DirectListener {
/**
* 定义此方法为队列test_springboot的监听器,一旦监听到新的消息,就会接受并处理
* @param message 消息内容
*/
@RabbitListener(queues = "test_springboot")
public void customer(Message message){
System.out.println(new String(message.getBody()));
}
}
3.启动服务。
可以看到,成功消费了消息。
消费并反馈
如果需要确保消息能够被消费者接受并处理,然后得到消费者的反馈,也是可以的。
1.定义生产者。
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RabbitMqSpringBootTests {
/**
* RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
*/
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 生产者
*/
@Test
void producer() {
// 会等待消费者消费然后返回响应结果
Object res = rabbitTemplate.convertSendAndReceive("amq.direct", "test_springboot_key", "Hello World!");
System.out.println("收到消费者响应:" + res);
}
}
2.定义生产者。
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 直连队列监听器
* @author CodeSail
*/
@Component
public class DirectListener {
/**
* 定义此方法为队列test_springboot的监听器,一旦监听到新的消息,就会接受并处理
* @param message 消息内容
*/
@RabbitListener(queues = "test_springboot")
public String customer(String message){
System.out.println("1号消息队列监听器:" + message);
return "收到!";
}
}
3.启动生产者发送消息。
可以看到,消费完成后接收到了反馈消息。
Json消息
1.引入依赖。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.14.2</version>
</dependency>
2.定义对象。
import lombok.Data;
/**
* 用户
*/
@Data
public class User {
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
}
3.定义Bean。
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* RabbitMQ配置类
*/
@Configuration
public class RabbitMqConfig {
...
/**
* 构建Json转换器
* @return Json转换器
*/
@Bean
public Jackson2JsonMessageConverter jackson2JsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
}
4.定义消费者。
import cn.codesail.rabbitmq.entity.User;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 直连队列监听器
*/
@Component
public class DirectListener {
/**
* 指定messageConverter为创建的Bean名称
* @param user 用户
*/
@RabbitListener(queues = "test_springboot", messageConverter = "jackson2JsonMessageConverter")
public void receiver(User user) {
System.out.println(user);
}
}
5.定义生产者。
import cn.codesail.rabbitmq.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RabbitMqSpringBootTests {
/**
* RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
*/
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 生产者
*/
@Test
void producer() {
// 发送Json消息
User user = new User();
user.setName("张三");
user.setAge(18);
rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", user);
}
}
6.启动生产者发送消息。
-
可以看到,对象转成了Json,消费者接收到Json转为的对象。
Spring Boot操作RabbitMQ是十分方便的,也是现在的主流,后续都用这种方式演示。
- 环境
- JDK 17.0.6
- Maven 3.6.3
- SpringBoot 3.0.4
- spring-boot-starter-amqp 3.0.4
- jackson-databind 2.14.2
工作队列模式
工作队列模式
工作队列模式结构图:
这种模式非常适合多个工人等待任务到来的场景。任务有多个,一个一个丢进消息队列,工人也有很多个,就可以将这些任务分配个各个工人,让他们各自负责一些任务,并且做的快的工人还可以多完成一些(能者多劳)。
要实现这种模式,只需要创建多个监听器即可。
先监听再发消息
这里先介绍先监听再发送消息的情况。
1.定义配置类
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* RabbitMQ配置类
*
* @author CodeSail
*/
@Configuration
public class RabbitMqConfig {
/**
* 定义交换机,可以很多个
* @return 交换机对象
*/
@Bean("directExchange")
public Exchange exchange(){
return ExchangeBuilder.directExchange("amq.direct").build();
}
/**
* 定义消息队列
* @return 消息队列对象
*/
@Bean("testQueue")
public Queue queue(){
return QueueBuilder
// 非持久化类型
.nonDurable("test_springboot")
.build();
}
/**
* 定义绑定关系
* @return 绑定关系
*/
@Bean
public Binding binding(@Qualifier("directExchange") Exchange exchange,
@Qualifier("testQueue") Queue queue){
// 将定义的交换机和队列进行绑定
return BindingBuilder
// 绑定队列
.bind(queue)
// 到交换机
.to(exchange)
// 使用自定义的routingKey
.with("test_springboot_key")
// 不设置参数
.noargs();
}
}
2.创建两个监听器,监听同一队列。
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 工作队列监听器
*
* @author 廖航
*/
@Component
public class WorkListener {
@RabbitListener(queues = "test_springboot")
public void receiver1(String message) {
System.out.println("1号监听器:" + message);
}
@RabbitListener(queues = "test_springboot")
public void receiver2(String message) {
System.out.println("2号监听器:" + message);
}
}
3.定义生产者。
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.TimeUnit;
@SpringBootTest
class RabbitMqSpringBootTests {
/**
* RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
*/
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 生产者
*/
@Test
void producer() throws InterruptedException {
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", "Hello World");
}
}
}
4.启动生产者发送消息。
可以看到,当同一个队列有多个监听器时,默认采用轮询的方式消费消息。
先发消息再监听
前面介绍了先监听再发消息的情况,下面介绍先发消息再监听的情况。
-
向队列中发送10条消息。
-
启动服务消费消息。
可以看到,如果是一开始就存在消息,会被一个消费者一次性全部消费,这是因为没有对消费者的Prefetch count(预获取数量,一次性获取消息的最大数量)进行限制。
默认的Prefetch count为250。
也就是说如果希望消费者一次只拿一个消息,而不是将所有的消息全部都获取,需要设置Prefetch count。
要对这个数量进行配置,需要在配置类中定义一个自定义的ListenerContainerFactory
,可以在这里设置消费者Channel的PrefetchCount的大小。
1.配置类中定义ListenerContainerFactory
。
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* RabbitMQ配置类
*
* @author CodeSail
*/
@Configuration
public class RabbitMqConfig {
@Autowired
private CachingConnectionFactory connectionFactory;
...
@Bean(name = "listenerContainer")
public SimpleRabbitListenerContainerFactory listenerContainer(){
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// 将PrefetchCount设定为1表示一次只能取一个
factory.setPrefetchCount(1);
return factory;
}
}
2.监听器中指定。
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 工作队列监听器
*
* @author 廖航
*/
@Component
public class WorkListener {
@RabbitListener(queues = "test_springboot", containerFactory = "listenerContainer")
public void receiver1(String message) {
System.out.println("1号监听器:" + message);
}
@RabbitListener(queues = "test_springboot", containerFactory = "listenerContainer")
public void receiver2(String message) {
System.out.println("2号监听器:" + message);
}
}
3.向队列中发送10条消息。
4.启动服务消费消息。
可以看到,两个监听器又实现了轮询消费消息。
Prefetch count设为了1。
至此,工作队列模式的两种情况就介绍完毕了。
- 环境
- JDK 17.0.6
- Maven 3.6.3
- SpringBoot 3.0.4
- spring-boot-starter-amqp 3.0.4
发布订阅模式
发布订阅模式结构图:
比如信用卡还款日临近了,那么就会给手机、邮箱发送消息,提示需要去还款了,但是手机短信和邮件发送并不一定是同一个业务提供的,但是现在又希望能够都去执行,就可以用到发布订阅模式,简而言之就是,发布一次,消费多个。
实现这种模式需要用到另一种类型的交换机,叫做fanout(扇出)类型,这是一种广播类型,消息会被广播到所有与此交换机绑定的消息队列中。
这里使用默认的扇出交换机:
1.定义配置类。
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
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;
/**
* RabbitMQ配置类
*/
@Configuration
public class RabbitMqConfig {
/**
* 定义交换机,可以很多个
* @return 交换机对象
*/
@Bean
public Exchange fanoutExchange(){
return ExchangeBuilder.fanoutExchange("amq.fanout").build();
}
/**
* 定义消息队列
* @return 消息队列对象
*/
@Bean
public Queue fanoutQueue1(){
return new Queue("fanoutQueue1");
}
/**
* 定义绑定关系
* @return 绑定关系
*/
@Bean
public Binding binding1(@Qualifier("fanoutExchange") Exchange exchange,
@Qualifier("fanoutQueue1") Queue queue){
// 将定义的交换机和队列进行绑定
return BindingBuilder
// 绑定队列
.bind(queue)
// 到交换机
.to(exchange)
// 使用自定义的routingKey
.with("")
// 不设置参数
.noargs();
}
/**
* 定义消息队列
* @return 消息队列对象
*/
@Bean
public Queue fanoutQueue2(){
return new Queue("fanoutQueue2");
}
/**
* 定义绑定关系
* @return 绑定关系
*/
@Bean
public Binding binding(@Qualifier("fanoutExchange") Exchange exchange,
@Qualifier("fanoutQueue2") Queue queue){
// 将定义的交换机和队列进行绑定
return BindingBuilder
// 绑定队列
.bind(queue)
// 到交换机
.to(exchange)
// 使用自定义的routingKey
.with("")
// 不设置参数
.noargs();
}
}
2.定义消费者。
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 发布订阅监听器
*/
@Component
public class FanoutListener {
@RabbitListener(queuesToDeclare = {@Queue("fanoutQueue1")})
public void receiver1(String message) {
System.out.println("1号监听器:" + message);
}
@RabbitListener(queuesToDeclare = {@Queue("fanoutQueue2")})
public void receiver2(String message) {
System.out.println("2号监听器:" + message);
}
}
为了避免监听时没有该队列而报错,可以采用queuesToDeclare = {@Queue("队列名称")}
的形式,这样如果没有该队列会自动创建该队列。
3. 定义生产者。
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.TimeUnit;
@SpringBootTest
class RabbitMqSpringBootTests {
/**
* RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
*/
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 生产者
*/
@Test
void producer() {
rabbitTemplate.convertAndSend("amq.fanout", "", "Hello World");
}
}
4.启动生产者,发送消息。
可以看到,发送一条消息,两个消费者都收到了消息,这就是发布订阅模式。
- 环境
- JDK 17.0.6
- Maven 3.6.3
- SpringBoot 3.0.4
- spring-boot-starter-amqp 3.0.4
路由模式
路由模式结构图:
1.定义配置类
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
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;
/**
* RabbitMQ配置类
*/
@Configuration
public class RabbitMqConfig {
/**
* 定义交换机,可以很多个
* @return 交换机对象
*/
@Bean
public Exchange directExchange(){
return ExchangeBuilder.directExchange("amq.direct").build();
}
/**
* 定义消息队列
* @return 消息队列对象
*/
@Bean
public Queue directQueue(){
return new Queue("directQueue");
}
/**
* 定义绑定关系
* @return 绑定关系
*/
@Bean
public Binding binding1(@Qualifier("directExchange") Exchange exchange,
@Qualifier("directQueue") Queue queue){
// 将定义的交换机和队列进行绑定
return BindingBuilder
// 绑定队列
.bind(queue)
// 到交换机
.to(exchange)
// 使用自定义的routingKey
.with("test_springboot_key1")
// 不设置参数
.noargs();
}
/**
* 定义绑定关系
* @return 绑定关系
*/
@Bean
public Binding binding(@Qualifier("directExchange") Exchange exchange,
@Qualifier("directQueue") Queue queue){
// 将定义的交换机和队列进行绑定
return BindingBuilder
// 绑定队列
.bind(queue)
// 到交换机
.to(exchange)
// 使用自定义的routingKey
.with("test_springboot_key2")
// 不设置参数
.noargs();
}
}
2.定义消费者。
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 直连队列监听器
*/
@Component
public class DirectListener {
/**
* 监听直连队列消息
*/
@RabbitListener(queues = "directQueue")
public void receiver(String message) {
System.out.println("直连队列接收到消息:" + message);
}
}
3.定义生产者。
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RabbitMqSpringBootTests {
/**
* RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
*/
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 生产者
*/
@Test
void producer() {
rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key1", "Hello World");
rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key2", "Hello World");
}
}
4.启动生产者,发送消息
可以看到,不同的routingKey都发送到了目标队列进行消费,这就是路由模式。
- 环境
- JDK 17.0.6
- Maven 3.6.3
- SpringBoot 3.0.4
- spring-boot-starter-amqp 3.0.4
主题模式
主题模式结构图:
主题模式实际上就是一种模糊匹配的模式,可以将routingKey
以模糊匹配的方式去进行转发。
可以使用*
或#
来表示:
*
:任意的一个单词。#
:0个或多个单词。
1.定义配置类。
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
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;
/**
* RabbitMQ配置类
*/
@Configuration
public class RabbitMqConfig {
/**
* 定义交换机,可以很多个
* @return 交换机对象
*/
@Bean
public Exchange topicExchange(){
return ExchangeBuilder.topicExchange("amq.topic").build();
}
/**
* 定义消息队列
* @return 消息队列对象
*/
@Bean
public Queue topicQueue(){
return new Queue("topicQueue");
}
/**
* 定义绑定关系
* @return 绑定关系
*/
@Bean
public Binding binding(@Qualifier("topicExchange") Exchange exchange,
@Qualifier("topicQueue") Queue queue){
// 将定义的交换机和队列进行绑定
return BindingBuilder
// 绑定队列
.bind(queue)
// 到交换机
.to(exchange)
// 使用自定义的routingKey
.with("*.test.#")
// 不设置参数
.noargs();
}
}
2. 定义消费者。
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 主题队列监听器
*/
@Component
public class TopicListener {
/**
* 监听主题队列消息
*/
@RabbitListener(queuesToDeclare = {@Queue("topicQueue")})
public void receiver(String message) {
System.out.println("主题队列接收到消息:" + message);
}
}
3.定义生产者。
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RabbitMqSpringBootTests {
/**
* RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
*/
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 生产者
*/
@Test
void producer() {
rabbitTemplate.convertAndSend("amq.topic", "test.1", "Hello World 1");
rabbitTemplate.convertAndSend("amq.topic", "1.test", "Hello World 2");
rabbitTemplate.convertAndSend("amq.topic", "1.test.1", "Hello World 3");
}
}
4.启动服务。
此时队列已创建。
绑定关系已设置。
启动生产者,发送消息。
可以看到,第一条消息的routingKey(test.1
)由于不匹配*.test.#
而没有被队列接收到。
除了这里使用的默认主题交换机之外,还有一个叫做amq.rabbitmq.trace
的交换机:
这是用于帮助记录和追踪生产者和消费者使用消息队列的交换机,它是一个内部的交换机,这里就不演示了。
- 环境
- JDK 17.0.6
- Maven 3.6.3
- SpringBoot 3.0.4
- spring-boot-starter-amqp 3.0.4
头部模式
头部模式是根据头部信息来决定的,在发送的消息中是可以携带一些头部信息的(类似于HTTP),可以根据这些头部信息来决定路由到哪一个消息队列中。
1.定义配置类。
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.HeadersExchange;
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;
/**
* RabbitMQ配置类
*/
@Configuration
public class RabbitMqConfig {
/**
* 定义交换机,可以很多个
* @return 交换机对象
*/
@Bean
public HeadersExchange headersExchange(){
return ExchangeBuilder.headersExchange("amq.headers").build();
}
/**
* 定义消息队列
* @return 消息队列对象
*/
@Bean
public Queue headersQueue(){
return new Queue("headersQueue");
}
/**
* 定义绑定关系
* @return 绑定关系
*/
@Bean
public Binding binding(@Qualifier("headersExchange") HeadersExchange exchange,
@Qualifier("headersQueue") Queue queue){
// 将定义的交换机和队列进行绑定
return BindingBuilder
// 绑定队列
.bind(queue)
// 到交换机
.to(exchange)
// .whereAny("a", "b").exist(); // 这个是只要存在任意一个指定的头部Key就行
// .whereAny(Collections.singletonMap("a", "b")).match(); // 传入Map也行,批量指定键值对
// .whereAll("a", "b").exist(); // 这个是必须存在所有指定的的头部Key
// .whereAll(Collections.singletonMap("a", "b")).match(); // 传入Map也行,批量指定键值对
.where("test").matches("hello"); // 比如我们现在需要消息的头部信息中包含test,并且值为hello才能转发给我们的消息队列
}
}
2.定义消费者。
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 头部监听器
*/
@Component
public class HeadersListener {
/**
* 监听头部队列消息
*/
@RabbitListener(queuesToDeclare = {@Queue("headersQueue")})
public void receiver(String message) {
System.out.println("头部队列接收到消息:" + message);
}
}
3.定义生产者。
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RabbitMqSpringBootTests {
/**
* RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
*/
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 生产者
*/
@Test
void producer() {
Message message = MessageBuilder.withBody("Hello World".getBytes()).setHeader("test","hello").build();
rabbitTemplate.convertAndSend("amq.headers", "", message);
}
}
4.启动生产者发送消息。
可以看到,通过头部的匹配,队列成功接收到了消息。
- 环境
- JDK 17.0.6
- Maven 3.6.3
- SpringBoot 3.0.4
- spring-boot-starter-amqp 3.0.4
死信队列
概述
消息队列中的数据,如果迟迟没有消费者来处理,就会一直占用消息队列的空间。
比如抢车票的场景,用户下单高铁票之后,会进行抢座,然后再进行付款,但是如果用户下单之后并没有及时的付款,这张票不可能一直让这个用户占用着,因为这样别人就买不到这张票了,所以会在一段时间后超时,让这张票可以继续被其他人购买。
这时,就可以使用死信队列,将那些用户超时未付款的或是用户主动取消的订单,进行进一步的处理。
那么如何构建这样的一种使用模式呢?实际上本质就是一个死信交换机+死信队列。
当正常队列中的消息被判定为死信时,会被发送到对应的死信交换机,然后再通过交换机发送到死信队列中,死信队列也有对应的消费者去处理消息。
判定为死信一般是3种情况:
- 消息被拒绝(
basic.reject
/basic.nack
),并且requeue = false
。 - 消息超时未消费。
- 消息队列达到最大长度。
产生死信
消息被拒绝
1.在配置类中创建一个新的死信交换机和死信队列,并进行绑定。
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* RabbitMQ配置类
*/
@Configuration
public class RabbitMqConfig {
...
/**
* 定义消息队列
* @return 消息队列对象
*/
@Bean("testQueue")
public Queue queue(){
return QueueBuilder
// 非持久化类型
.nonDurable("test_springboot")
// 指定死信交换机
.deadLetterExchange("dl.direct")
// 指定死信RoutingKey
.deadLetterRoutingKey("dl_test_springboot_key")
.build();
}
/**
* 构建死信交换机
* @return 死信交换机
*/
@Bean
public Exchange dlExchange(){
// 创建一个新的死信交换机
return ExchangeBuilder.directExchange("dl.direct").build();
}
/**
* 构建死信队列
* @return 死信队列
*/
@Bean
public Queue dlQueue(){
return QueueBuilder
.nonDurable("dl_test_springboot")
.build();
}
/**
* 死信交换机和死信队列绑定
* @param exchange 死信交换机
* @param queue 死信队列
* @return 绑定对象
*/
@Bean
public Binding dlBinding(@Qualifier("dlExchange") Exchange exchange,
@Qualifier("dlQueue") Queue queue){
return BindingBuilder
.bind(queue)
.to(exchange)
.with("dl_test_springboot_key")
.noargs();
}
...
}
2.监听正常队列和死信队列消息。
import cn.codesail.rabbitmq.entity.User;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
/**
* 直连队列监听器
*/
@Component
public class DirectListener {
/**
* 监听正常队列消息
*/
@RabbitListener(queues = "test_springboot", messageConverter = "jackson2JsonMessageConverter")
public void receiver(Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, User user) throws Exception {
// 拒绝消息。第二个参数为true则消息返回队列,第二个参数为false则消息不返回队列,成为死信
channel.basicReject(deliveryTag, false);
System.out.println("正常队列接收到消息:" + user);
}
/**
* 监听死信队列消息
*/
@RabbitListener(queues = "dl_test_springboot", messageConverter = "jackson2JsonMessageConverter")
public void receiverDl(User user) {
System.out.println("死信队列接收到消息:" + user);
}
}
正常队列消息的监听种拒绝了消息,且不返回队列,成为了死信,就会被死信队列的监听接收到。
3.删除原队列。删除了原队列才能创建与死信队列绑定的队列。
4.实现生产者。
import cn.codesail.rabbitmq.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RabbitMqSpringBootTests {
/**
* RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
*/
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 生产者
*/
@Test
void producer() {
// 发送Json消息
User user = new User();
user.setName("张三");
user.setAge(18);
rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", user);
}
}
5.启动生产者发送消息:
可以看到,死信队列接收到了消息。
消息超时未消费
1.设定队列TTL值。
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* RabbitMQ配置类
*
* @author CodeSail
*/
@Configuration
public class RabbitMqConfig {
...
/**
* 定义消息队列
* @return 消息队列对象
*/
@Bean("testQueue")
public Queue queue(){
return QueueBuilder
// 非持久化类型
.nonDurable("test_springboot")
// 指定死信交换机
.deadLetterExchange("dl.direct")
// 指定死信RoutingKey
.deadLetterRoutingKey("dl_test_springboot_key")
// 如果5秒没处理,就自动删除
.ttl(5000)
.build();
}
...
}
2.取消正常队列监听。
import cn.codesail.rabbitmq.entity.User;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
/**
* 直连队列监听器
*/
@Component
public class DirectListener {
/**
* 监听正常队列消息
*/
// @RabbitListener(queues = "test_springboot", messageConverter = "jackson2JsonMessageConverter")
// public void receiver(Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, User user) throws Exception {
// // 拒绝消息。第二个参数为true则消息返回队列,第二个参数为false则消息不返回队列,成为死信
// channel.basicReject(deliveryTag, false);
// System.out.println("正常队列接收到消息:" + user);
// }
/**
* 监听死信队列消息
*/
@RabbitListener(queues = "dl_test_springboot", messageConverter = "jackson2JsonMessageConverter")
public void receiverDl(User user) {
System.out.println("死信队列接收到消息:" + user);
}
}
3.实现生产者。
4.删除原队列。删除了原队列才能创建与死信队列绑定的设定了TTL的队列。
5.启动服务,监听消息。
6.启动生产者发送消息,等待5秒:
可以看到,死信队列接收到了消息。
消息队列达到最大长度
1.设置队列最大消息长度。
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* RabbitMQ配置类
*/
@Configuration
public class RabbitMqConfig {
...
/**
* 定义消息队列
* @return 消息队列对象
*/
@Bean("testQueue")
public Queue queue(){
return QueueBuilder
// 非持久化类型
.nonDurable("test_springboot")
// 指定死信交换机
.deadLetterExchange("dl.direct")
// 指定死信RoutingKey
.deadLetterRoutingKey("dl_test_springboot_key")
// 最大长度设定为3
.maxLength(3)
.build();
}
...
}
2.取消正常队列监听。
import cn.codesail.rabbitmq.entity.User;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* 直连队列监听器
*/
@Component
public class DirectListener {
/**
* 监听正常队列消息
*/
// @RabbitListener(queues = "test_springboot", messageConverter = "jackson2JsonMessageConverter")
// public void receiver(User user) {
// System.out.println("正常队列接收到消息:" + user);
// }
/**
* 监听死信队列消息
*/
@RabbitListener(queues = "dl_test_springboot", messageConverter = "jackson2JsonMessageConverter")
public void receiverDl(User user) {
System.out.println("死信队列接收到消息:" + user);
}
}
3.删除原队列。删除了原队列才能创建与死信队列绑定的设定了最大长度的队列。
4.定义生产者。
import cn.codesail.rabbitmq.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.TimeUnit;
@SpringBootTest
class RabbitMqSpringBootTests {
/**
* RabbitTemplate封装了大量的RabbitMQ操作,已经由Starter提供,因此直接注入使用即可
*/
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 生产者
*/
@Test
void producer() throws InterruptedException {
for (int i = 0; i < 4; i++) {
User user = new User();
user.setName("张三" + i);
user.setAge(18);
rabbitTemplate.convertAndSend("amq.direct", "test_springboot_key", user);
}
}
}
5.启动生产者发送消息。
可以看到,队列的第一个元素被挤出成为了死信。
队列就类似于一个管道,当管道的人占满了,最后进去的人就会把最前面的人挤出去。
- 环境
- JDK 17.0.6
- Maven 3.6.3
- SpringBoot 3.0.4
- spring-boot-starter-amqp 3.0.4
- jackson-databind 2.14.2