RabbitMQ讲解-基础篇
安装部署
第一种方式:通过Podman Desktop(可视化界面) + Podman Machine(windows连接WSL2) + WSL2(虚拟机) + Podman(类似docker的工具)部署RabbotMQ
- 下载镜像
- 运行容器
第二种方式:通过docker部署
确保虚拟机已经安装好docker后,执行:
bash
# 一条命令,先拉取镜像再运行成容器
docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management
访问
http://localhost:15672(默认账号密码guest/guest)进入管理界面

基本概念
RabbitMQ 是一款基于 AMQP(Advanced Message Queuing Protocol)协议的开源消息队列,以其轻量、灵活、支持多种消息模式著称,尤其适合复杂业务场景下的服务解耦和异步通信
引入了新概念
| 概念 | 作用 |
|---|---|
| 生产者 | 发送方,需考虑发送失败 |
| 消费者 | 接收方 |
| 队列(Queue) | 存储消息,需考虑溢出、持久化 |
| 交换机(Exchange) | 接收生产者发送的消息,根据 "路由规则" 转发到对应的队列;本身不存消息 |
| 绑定(Binding) | 交换机和队列之间绑定 |
| 路由键(Routing Key) | 消息的 "地址标识",交换机根据它和绑定规则决定消息发往哪个队列 |
| 虚拟主机(VHost) | 隔离不同业务的消息空间(类似数据库的 Schema),提高安全性 |
队列、交换机、绑定、路由键、虚拟主机等都可以通过可视化管理界面定义
消息模式
-
简单模式(一对一)
场景:单个生产者发送消息给单个消费者
流程:生产者 → 队列 → 消费者

-
工作队列模式(一对多)
场景:多个消费者共同处理一个队列的消息,实现负载均衡(如秒杀请求分布式处理)
特点:一条消息只会被一个消费者处理,避免重复消费

-
发布 / 订阅模式(广播)
场景:一条消息需要被多个消费者接收(如订单创建后,通知库存、物流、积分服务)
原理:使用
Fanout类型交换机,消息会转发给所有绑定到该交换机的队列,每个消费者监听一个队列
-
路由模式(按规则转发)
场景:消息需要按特定规则分发
原理:使用
Direct类型交换机,消息的路由键需与绑定的路由键完全匹配才会转发既可以实现消息由交换机只发给某一个队列,也可以实现上面的广播

-
主题模式(模糊匹配)
场景:更灵活的规则匹配
原理:使用
Topic类型交换机,支持通配符(*匹配一个单词,#匹配多个单词),例如user.#匹配user.login、user.register等
快速使用
-
引入依赖
基于Spring AMQP
xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> -
配置yml
ymlspring: rabbitmq: host: localhost port: 5672 username: guest password: guest virtual-host: / -
定义交换机、队列及绑定关系
第一种方法通过配置类定义
java@Configuration public class RabbitConfig { // 声明交换机 @Bean public TopicExchange orderExchange() { return ExchangeBuilder.topicExchange("order.exchange").durable(true).build(); } // 声明队列 @Bean public Queue stockQueue() { return QueueBuilder.durable("stock.queue").build(); } // 绑定交换机和队列(路由键:order.stock) @Bean public Binding stockBinding(TopicExchange orderExchange, Queue stockQueue) { return BindingBuilder.bind(stockQueue).to(orderExchange).with("order.stock"); } }第二种通过注解定义
java// 消费者类中直接声明(发送消息时自动创建交换机/队列) @RabbitListener(bindings = @QueueBinding( value = @Queue(value = "stock.queue", durable = "true"), // 队列 exchange = @Exchange(value = "order.exchange", type = ExchangeTypes.TOPIC), // 交换机 key = "order.stock" // 路由键 )) public void handleStock(Long orderId) { // 处理业务 } -
发送消息
java@Service public class OrderService { @Autowired private RabbitTemplate rabbitTemplate; public void createOrder(Long orderId) { // 发送消息(交换机、路由键、消息体) rabbitTemplate.convertAndSend("order.exchange", "order.stock", orderId); } } -
消费消息
java@Service public class StockService { @RabbitListener(queues = "stock.queue") public void handleStock(Long orderId) { // 处理库存扣减逻辑 System.out.println("扣减订单:" + orderId + " 的库存"); } }
详细讲解
可视化界面
-
Overview(总览)
涉及节点,和部署集群相关

-
Connections(连接)
在springboot单节点项目中,连接就只有我们项目这一个
如果是在springcloud分布式项目中,多个模块连接到消息队列中,每个模块都是一个连接

-
Channels(通道)

-
Exchanges(交换机)
重点

-
Queues(队列)
重点

-
Admin(用户管理)

虚拟主机隔离各个用户,默认虚拟主机为斜杠 /
最好每个用户对应单独一个虚拟主机
虚拟主机配置


模型使用
-
简单模式
一对一模式
发布:
java@SpringBootTest public class SpringAmqpTest { @Resource private RabbitTemplate rabbitTemplate; @Test public void testSendMessage() { String queueName = "simple.queue"; String message = "Hello World!"; rabbitTemplate.convertAndSend(queueName, message); } }监听:
java@Component public class MqListener { @RabbitListener(queues = "simple.queue") public void receiveMessage(String message) { System.out.println("Received <" + message + ">"); } }基本也没什么考虑的东西
现在发布者代码写在测试类中,需注意测试类的写法(@SpringBootTest + @Resource + @Test)
实际项目中发布者肯定是对应某个接口,接口接收请求,然后调用发布者方法发布消息到消息队列中
对应的监听者就自动获取消息并执行
注意监听者要@Component
测试时,我们直接运行测试类的方法即可,Spring会自动注入需要的bean
-
工作队列模式
多个监听者监听一个发布者
此时就涉及到负载均衡的问题
默认中,不管监听者中的Thread.sleep多久(耗时多久),每个监听者最后处理的消息数量都是一样的
消息队列将消息均匀轮询发送给所有监听者
但这并不好,因为处理慢的和处理快的会被分配一样的工作内容,最后快的做完了,慢的还在继续很久,导致消息被堆积
要提高效率,就应该能者多劳
只需添加配置到application.yml即可:
ymlspring: rabbitmq: host: localhost port: 5672 username: slc password: slc virtual-host: /slc listener: simple: prefetch: 1 # 每次只能获取一条消息,处理完才获取下一条发布者:
java@SpringBootTest public class SpringAmqpTest { @Resource private RabbitTemplate rabbitTemplate; @Test public void testSendMessage2() { String name = "work.queue"; String message = "Hello World!"; for (int i = 0; i < 50; i++){ rabbitTemplate.convertAndSend(name, message + i); } } }监听者:
java@Component public class WorkListener { @RabbitListener(queues = "work.queue") public void receiveMessage(String message) throws InterruptedException { System.out.println("Received <" + message + ">"); Thread.sleep(100); } @RabbitListener(queues = "work.queue") public void receiveMessage2(String message) throws InterruptedException { System.out.println("Received <" + message + ">"); Thread.sleep(1000); } }但通常,发布者并不直接将消息发送到队列,而是发给交换机,也就是下面三种模式
-
发布 / 订阅模式
Fanout交换机
广播模式,不筛选,交换机直接将消息发给所有队列
先在图形化界面中定义多个队列、一个Fanout交换机,并建立二者关系
- 队列:fanout.queue1、fanout.queue1
- 交换机:fanout.exchange
建立监听者监听这个多个队列
建立发布者向交换机发送消息(不再直接给队列发送消息)
监听者:
java@Component public class FanoutListener { @RabbitListener(queues = "fanout.queue1") public void receiveMessage1(String message) { System.out.println("fanout.queue1:Received <" + message + ">"); } @RabbitListener(queues = "fanout.queue2") public void receiveMessage2(String message) { System.out.println("fanout.queue2:Received <" + message + ">"); } }发布者:
java// 广播模式Fanout @Test public void testSendMessage3() { String name = "fanout.exchange"; String message = "Hello World!"; rabbitTemplate.convertAndSend(name, "", message); }广播,两个都收到:

需要注意的就是,发布者就是向交换机发送消息,第二个参数传空或者null
下面两种模式,就会传递第二个参数,对应路由键,让发布者将消息通过交换机,只发送给部分匹配的队列中
-
路由模式
Direct交换机
这种模式就既可以实现上面的广播模式,也可以实现发送给部分队列
首先需要创建队列、交换机,并在交换机中指定队列的路由规则(添加路由键,这里可多个)
-
队列:direct.queue1、direct.queue2
-
交换机:direct.exchange
-
交换机绑定队列时添加路由键:

监听者:
java@Component public class DirectListener { @RabbitListener(queues = "direct.queue1") public void receiveMessage1(String message) { System.out.println("direct.queue1:Received <" + message + ">"); } @RabbitListener(queues = "direct.queue2") public void receiveMessage2(String message) { System.out.println("direct.queue2:Received <" + message + ">"); } }发布者:
java// 路由模式 @Test public void testSendMessage4() { String name = "direct.exchange"; String message = "Hello World! red"; String message1 = "Hello World! blue"; rabbitTemplate.convertAndSend(name, "red", message); rabbitTemplate.convertAndSend(name, "blue", message1); }
red两个队列都收到了
bule只有队列1收到了
-
-
主题模式
Topic交换机
更灵活的路由模式
*匹配一个单词,#匹配多个单词发布时指定准确的key,绑定时可使用通配符(实现广播效果)
队列:topic.queue1、topic.queue1
交换机:topic.exchange

监听者:
java@Component public class TopicListener { @RabbitListener(queues = "topic.queue1") public void receiveMessage1(String message) { System.out.println("topic.queue1:Received <" + message + ">"); } @RabbitListener(queues = "topic.queue2") public void receiveMessage2(String message) { System.out.println("topic.queue2:Received <" + message + ">"); } }发布者:
java// 主题模式 @Test public void testSendMessage5() { String name = "topic.exchange"; String message = "Hello World! china.one"; String message1 = "Hello World! china.news"; rabbitTemplate.convertAndSend(name, "china.one", message); rabbitTemplate.convertAndSend(name, "china.news", message1); }
china.one只有队列1收到了
china.news两个都收到了
定义交换机和队列
上面讲解中都是直接通过可视化界面来定义队列、交换机,并实现绑定
但实际使用中肯定是要用代码来实现的,这就对应两种方式:
-
通过配置文件
对应三个类
- Queue:声明队列
- Exchange:声明交换机
- Binding:声明队列和交换机的绑定关系
java@Configuration public class FanoutConfiguration { // 定义广播交换机 @Bean public FanoutExchange fanoutExchange() { return new FanoutExchange("fanout.exchange"); } // 定义路由交换机 @Bean public DirectExchange directExchange() { return new DirectExchange("direct.exchange"); } // 定义主题交换机 @Bean public TopicExchange topicExchange() { return new TopicExchange("topic.exchange"); } // 定义队列 @Bean public Queue fanoutQueue() { return new Queue("fanout.queue"); } // 绑定队列到广播交换机(通过参数绑定) @Bean public Binding fanoutBinding(FanoutExchange fanoutExchange, Queue fanoutQueue) { return BindingBuilder.bind(fanoutQueue).to(fanoutExchange); } // 绑定队列到路由交换机(通过参数绑定) @Bean public Binding directBinding(DirectExchange directExchange, Queue fanoutQueue) { return BindingBuilder.bind(fanoutQueue).to(directExchange).with("red"); } // 定义队列1 @Bean public Queue fanoutQueue1() { return new Queue("fanout.queue1"); } // 绑定队列1到交换机(通过方法绑定,不是直接调用本类的方法,而是也去spring中找,和上面的写法作用一样) @Bean public Binding fanoutBinding1() { return BindingBuilder.bind(fanoutQueue1()).to(fanoutExchange()); } }这样会比较复杂,包括定义交换机、队列等,在绑定关系时,如果要
-
直接写在监听者上面的注解中
java// 通过注解方式,快速定义交换机、队列、绑定关系 @RabbitListener(bindings = @QueueBinding( // 定义绑定 value = @Queue(name = "direct.queue1",durable = "true"), // 定义队列,选择持久化(durable) exchange = @Exchange(name = "direct.exchange", type = ExchangeTypes.DIRECT), // 定义交换机,选择种类 key = {"red","blue"} // 定义路由键 )) public void receiveMessage3(String message) { System.out.println("direct.queue1:Received <" + message + ">"); }
总结:
关于bean:
- Queue
- FanoutExchange
- DirectExchange
- TopicExchange
- Binding
关于注解:
@RabbitListener
@QueueBinding
@Queue
@Exchange
消息转换器
我们之前,发布者都是直接向交换机、队列中发送字符串作为消息,但平时更多的肯定是对象类型
如果不使用消息转换器,传递对象时就会乱码
因此就需要用消息转换器
简单来说,当传递的消息是字节类型、字符串类型,则可以直接传输
而当我们传递对象时,会调用jdk自带的ObjectOutputStream将对象转为字节码,导致乱码
我们要避免乱码,就需要自定义消息转换器
例如使用jackson的Jackson2JsonMessageConverter
- 导入依赖:(Springboot中自带)
xml
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
- 在publicsher和consumer中都配置MessageConverter
java
@Bean
public MessageConverter jacksonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
// 配置消息转换器时需要注意MessageConverter要选择amqp的,指定是给amqp定义转换器
//import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
//import org.springframework.amqp.support.converter.MessageConverter;
消息id
消息id由发布者生成,若我们没有自定义消息id,RabbitTemplate会自动创建
可以通过在监听者的方法参数中直接获取到消完整的消息对象,或者消息id
如:
java
@Component
public class OrderConsumer {
//直接获取消息id
@RabbitListener(queues = "queue.order")
public void receiveOrder(Order order, @Header(AmqpHeaders.MESSAGE_ID) String messageId) {
System.out.println("消息ID: " + messageId);
System.out.println("订单内容: " + order);
}
// 或者获取完整的Message对象,再从Message对象中拿到消息id
@RabbitListener(queues = "queue.order")
public void receiveOrder(Message message) {
String messageId = message.getMessageProperties().getMessageId();
Order order = (Order) rabbitTemplate.getMessageConverter()
.fromMessage(message);
System.out.println("消息ID: " + messageId);
System.out.println("订单内容: " + order);
}
}