RabbitMQ 入门实战教程
一、什么是 RabbitMQ?
RabbitMQ 是一个开源的消息代理(Message Broker),采用 Erlang 语言编写,基于 AMQP(Advanced Message Queuing Protocol)协议实现。它负责接收、存储和转发消息,是微服务架构中异步解耦的核心组件。
1.1 为什么需要消息队列?
场景:用户下单后需要发送短信和邮件通知
- 同步调用:下单接口等待短信 + 邮件发送完毕才返回,响应慢
- 引入 MQ:下单后立即返回,MQ 异步通知短信和邮件服务
核心优势:
| 优势 | 说明 |
|---|---|
| 异步处理 | 请求无需等待所有下游完成,提升响应速度 |
| 流量削峰 | 秒杀场景下请求先入队列,后端按能力消费 |
| 系统解耦 | 生产者和消费者互不感知,可独立扩缩容 |
| 可靠性 | 消息持久化 + 确认机制,保证不丢失 |
1.2 核心概念
+-----------+
| Producer | ----- 消息发布者
+-----+-----+
|
v
+-----------+-----------+
| Exchange | ----- 交换机(路由消息)
+-----------+-----------+
|
+---------------+----------------+
| | |
v v v
+-----------+ +-----------+ +-----------+
| Queue | | Queue | | Queue | ----- 队列(存储消息)
+-----------+ +-----------+ +-----------+
| | |
v v v
+-----------+ +-----------+ +-----------+
| Consumer | | Consumer | | Consumer | ----- 消息消费者
+-----------+ +-----------+ +-----------+
| 概念 | 说明 |
|---|---|
| Producer | 消息的生产者/发布者 |
| Consumer | 消息的消费者/订阅者 |
| Queue | 队列,存储消息的缓冲区 |
| Exchange | 交换机,接收消息并按路由规则分发到队列 |
| Binding | 绑定,定义 Exchange 与 Queue 之间的路由关系 |
| Routing Key | 路由键,Exchange 根据它决定将消息投递到哪个队列 |
| Connection | TCP 连接 |
| Channel | 在 Connection 上建立的虚拟连接,复用 TCP |
二、安装 RabbitMQ
2.1 Docker 安装(推荐)
bash
docker run -d \
--name rabbitmq \
-p 5672:5672 \
-p 15672:15672 \
-p 25672:25672 \
-e RABBITMQ_DEFAULT_USER=admin \
-e RABBITMQ_DEFAULT_PASS=admin123 \
rabbitmq:3.13-management
端口说明:
| 端口 | 用途 |
|---|---|
| 5672 | AMQP 协议端口(客户端连接) |
| 15672 | Web 管理控制台 |
| 25672 | Erlang 节点间通信 |
2.2 直接安装
- 安装 Erlang(RabbitMQ 依赖)
- 下载 RabbitMQ Server
- 启动后访问
http://localhost:15672,账号密码guest/guest
2.3 启用管理插件
bash
rabbitmq-plugins enable rabbitmq_management
三、六种工作模式
RabbitMQ 提供了多种消息模型,下面逐一讲解。
3.1 Hello World(简单模式)
一个生产者 → 一个队列 → 一个消费者。
[P] ----> [Queue] ----> [C]
生产者:
java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer {
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setUsername("admin");
factory.setPassword("admin123");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "Hello RabbitMQ!";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] 发送消息:" + message);
}
}
}
消费者:
java
import com.rabbitmq.client.*;
public class Consumer {
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setUsername("admin");
factory.setPassword("admin123");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] 收到消息:" + message);
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});
}
}
3.2 Work Queues(工作队列)
一个生产者 → 一个队列 → 多个消费者(竞争消费)。
[P] ----> [Queue] ----> [C1]
\---> [C2]
适用于任务分发场景,默认采用轮询分发,每个消费者平均获取消息。
消息确认 & 公平分发:
java
// 每次只处理一条消息,处理完再接受下一条
channel.basicQos(1);
// 手动 ACK
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME, autoAck, deliverCallback, consumerTag -> {});
// 在 deliverCallback 中确认
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
3.3 Publish / Subscribe(发布订阅模式)
一个生产者 → 交换机 → 多个队列 → 多个消费者。
[P] ----> [Fanout Exchange] ----> [Queue1] ----> [C1]
\---> [Queue2] ----> [C2]
交换机类型:Fanout(广播),将消息发送给所有绑定的队列。
java
// 生产者 - 声明交换机
channel.exchangeDeclare("logs", BuiltinExchangeType.FANOUT);
String message = "广播消息";
channel.basicPublish("logs", "", null, message.getBytes());
// 消费者 - 创建临时队列并绑定
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, "logs", "");
3.4 Routing(路由模式)
[P] ----> [Direct Exchange] ----> [Queue1] routingKey=error ----> [C1]
\---> [Queue2] routingKey=info \
routingKey=warning ----> [C2]
routingKey=error
交换机类型:Direct,根据 Routing Key 精确匹配。
java
// 生产者
channel.exchangeDeclare("direct_logs", BuiltinExchangeType.DIRECT);
channel.basicPublish("direct_logs", "error", null, message.getBytes());
// 消费者 - 绑定特定 routing key
channel.queueBind(queueName, "direct_logs", "error");
channel.queueBind(queueName, "direct_logs", "warning");
3.5 Topics(主题模式)
[P] ----> [Topic Exchange] ----> [Queue1] *.orange.* ----> [C1]
\---> [Queue2] *.*.rabbit ----> [C2]
\---> [Queue3] lazy.# ----> [C3]
交换机类型:Topic,根据 Routing Key 模糊匹配。
通配符规则:
| 通配符 | 含义 |
|---|---|
* |
匹配一个单词 |
# |
匹配零个或多个单词 |
示例:
java
// Routing Key 格式:<速度>.<颜色>.<动物>
// 生产者
channel.exchangeDeclare("topic_logs", BuiltinExchangeType.TOPIC);
channel.basicPublish("topic_logs", "quick.orange.rabbit", null, message.getBytes());
// 消费者1 - 匹配所有橙色动物
channel.queueBind(queueName, "topic_logs", "*.orange.*");
// 消费者2 - 匹配所有兔子(任意颜色速度)
channel.queueBind(queueName, "topic_logs", "*.*.rabbit");
// 消费者3 - 匹配 lazy 开头的所有
channel.queueBind(queueName, "topic_logs", "lazy.#");
3.6 Headers(头部模式)
不依赖 Routing Key,而是根据消息的 Headers 属性匹配。性能较低,实际使用较少。
java
Map<String, Object> headers = new HashMap<>();
headers.put("x-match", "all"); // all=全部匹配, any=匹配任一
headers.put("format", "pdf");
headers.put("type", "report");
channel.queueBind(queueName, "headers_exchange", "", headers);
四、Spring Boot 整合 RabbitMQ
4.1 引入依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
4.2 配置
yaml
spring:
rabbitmq:
host: localhost
port: 5672
username: admin
password: admin123
virtual-host: /
listener:
simple:
acknowledge-mode: manual # 手动确认
prefetch: 1 # 每次拉取一条
retry:
enabled: true # 消费失败重试
max-attempts: 3
initial-interval: 2000
template:
retry:
enabled: true # 发送重试
max-attempts: 3
4.3 配置类
java
package com.example.rabbitmq.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
// ========== Direct 模式 ==========
public static final String DIRECT_EXCHANGE = "direct.exchange";
public static final String DIRECT_QUEUE = "direct.queue";
public static final String DIRECT_ROUTING_KEY = "direct.key";
@Bean
public Queue directQueue() {
return QueueBuilder.durable(DIRECT_QUEUE).build();
}
@Bean
public DirectExchange directExchange() {
return ExchangeBuilder.directExchange(DIRECT_EXCHANGE).durable(true).build();
}
@Bean
public Binding directBinding(Queue directQueue, DirectExchange directExchange) {
return BindingBuilder.bind(directQueue)
.to(directExchange)
.with(DIRECT_ROUTING_KEY);
}
// ========== Topic 模式 ==========
public static final String TOPIC_EXCHANGE = "topic.exchange";
public static final String TOPIC_QUEUE = "topic.queue";
public static final String TOPIC_ROUTING_KEY = "topic.#";
@Bean
public Queue topicQueue() {
return QueueBuilder.durable(TOPIC_QUEUE).build();
}
@Bean
public TopicExchange topicExchange() {
return ExchangeBuilder.topicExchange(TOPIC_EXCHANGE).durable(true).build();
}
@Bean
public Binding topicBinding(Queue topicQueue, TopicExchange topicExchange) {
return BindingBuilder.bind(topicQueue)
.to(topicExchange)
.with(TOPIC_ROUTING_KEY);
}
// ========== Fanout 模式 ==========
public static final String FANOUT_EXCHANGE = "fanout.exchange";
public static final String FANOUT_QUEUE_A = "fanout.queue.a";
public static final String FANOUT_QUEUE_B = "fanout.queue.b";
@Bean
public Queue fanoutQueueA() {
return QueueBuilder.durable(FANOUT_QUEUE_A).build();
}
@Bean
public Queue fanoutQueueB() {
return QueueBuilder.durable(FANOUT_QUEUE_B).build();
}
@Bean
public FanoutExchange fanoutExchange() {
return ExchangeBuilder.fanoutExchange(FANOUT_EXCHANGE).durable(true).build();
}
@Bean
public Binding fanoutBindingA(Queue fanoutQueueA, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueueA).to(fanoutExchange);
}
@Bean
public Binding fanoutBindingB(Queue fanoutQueueB, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueueB).to(fanoutExchange);
}
}
4.4 消息发送
java
package com.example.rabbitmq.service;
import com.example.rabbitmq.config.RabbitConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@RequiredArgsConstructor
public class MessageSender {
private final RabbitTemplate rabbitTemplate;
public void sendDirect(String message) {
rabbitTemplate.convertAndSend(
RabbitConfig.DIRECT_EXCHANGE,
RabbitConfig.DIRECT_ROUTING_KEY,
message
);
log.info("Direct 消息发送成功:{}", message);
}
public void sendTopic(String routingKey, String message) {
rabbitTemplate.convertAndSend(
RabbitConfig.TOPIC_EXCHANGE,
routingKey,
message
);
log.info("Topic 消息发送成功:[{}] {}", routingKey, message);
}
public void sendFanout(String message) {
rabbitTemplate.convertAndSend(
RabbitConfig.FANOUT_EXCHANGE,
"",
message
);
log.info("Fanout 广播消息发送成功:{}", message);
}
}
4.5 消息接收
java
package com.example.rabbitmq.listener;
import com.example.rabbitmq.config.RabbitConfig;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MessageListener {
@RabbitListener(queues = RabbitConfig.DIRECT_QUEUE)
public void handleDirect(String message, Channel channel, Message msg) {
try {
log.info("Direct 消费者收到消息:{}", message);
// 手动确认
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
log.error("消息处理失败", e);
try {
// 重回队列或丢弃
channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
} catch (Exception ignored) {}
}
}
@RabbitListener(queues = RabbitConfig.TOPIC_QUEUE)
public void handleTopic(String message) {
log.info("Topic 消费者收到消息:{}", message);
}
@RabbitListener(queues = RabbitConfig.FANOUT_QUEUE_A)
public void handleFanoutA(String message) {
log.info("Fanout 队列A收到消息:{}", message);
}
@RabbitListener(queues = RabbitConfig.FANOUT_QUEUE_B)
public void handleFanoutB(String message) {
log.info("Fanout 队列B收到消息:{}", message);
}
}
4.6 Controller 测试
java
package com.example.rabbitmq.controller;
import com.example.rabbitmq.service.MessageSender;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/mq")
@RequiredArgsConstructor
public class MqController {
private final MessageSender messageSender;
@GetMapping("/direct")
public String direct(@RequestParam(defaultValue = "Hello Direct") String msg) {
messageSender.sendDirect(msg);
return "Direct 消息已发送";
}
@GetMapping("/topic")
public String topic(
@RequestParam(defaultValue = "topic.test.hello") String routingKey,
@RequestParam(defaultValue = "Hello Topic") String msg) {
messageSender.sendTopic(routingKey, msg);
return "Topic 消息已发送";
}
@GetMapping("/fanout")
public String fanout(@RequestParam(defaultValue = "Hello Fanout") String msg) {
messageSender.sendFanout(msg);
return "Fanout 广播消息已发送";
}
}
五、死信队列(DLQ)
死信队列用于处理消费失败的消息,是保证消息可靠性的重要机制。
5.1 消息变为死信的三种情况
- 消息被消费者拒绝(
basicNack/basicReject)且requeue=false - 消息 TTL 过期
- 队列达到最大长度
5.2 死信队列配置
java
@Configuration
public class DlxConfig {
// 业务队列
public static final String BUSINESS_QUEUE = "business.queue";
public static final String BUSINESS_EXCHANGE = "business.exchange";
public static final String BUSINESS_ROUTING_KEY = "business.key";
// 死信队列
public static final String DLX_EXCHANGE = "dlx.exchange";
public static final String DLX_QUEUE = "dlx.queue";
public static final String DLX_ROUTING_KEY = "dlx.key";
@Bean
public Queue businessQueue() {
return QueueBuilder.durable(BUSINESS_QUEUE)
.deadLetterExchange(DLX_EXCHANGE) // 指定死信交换机
.deadLetterRoutingKey(DLX_ROUTING_KEY) // 指定死信路由键
.ttl(60000) // 消息过期时间 60s
.maxLength(10000) // 最大消息数
.build();
}
@Bean
public DirectExchange businessExchange() {
return ExchangeBuilder.directExchange(BUSINESS_EXCHANGE).durable(true).build();
}
@Bean
public Binding businessBinding() {
return BindingBuilder.bind(businessQueue())
.to(businessExchange())
.with(BUSINESS_ROUTING_KEY);
}
// 死信队列声明
@Bean
public Queue dlxQueue() {
return QueueBuilder.durable(DLX_QUEUE).build();
}
@Bean
public DirectExchange dlxExchange() {
return ExchangeBuilder.directExchange(DLX_EXCHANGE).durable(true).build();
}
@Bean
public Binding dlxBinding() {
return BindingBuilder.bind(dlxQueue())
.to(dlxExchange())
.with(DLX_ROUTING_KEY);
}
}
六、延迟队列
RabbitMQ 本身没有延迟队列功能,通过死信队列 或官方延迟插件实现。
6.1 使用死信队列实现延迟
生产者发送消息时设置 TTL,消息过期后自动进入死信队列,实现延迟消费。
6.2 使用延迟队列插件
bash
# 下载插件
docker exec -it rabbitmq rabbitmq-plugins enable rabbitmq_delayed_message_exchange
java
@Bean
public CustomExchange delayExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange("delay.exchange", "x-delayed-message", true, false, args);
}
// 发送延迟消息
public void sendDelay(String message, long delayMillis) {
MessageProperties props = new MessageProperties();
props.setDelay(Math.toIntExact(delayMillis));
Message msg = MessageBuilder.withBody(message.getBytes()).andProperties(props).build();
rabbitTemplate.send("delay.exchange", "delay.key", msg);
}
七、消息可靠性保障
7.1 发送端确认
yaml
spring:
rabbitmq:
publisher-confirm-type: correlated # 确认模式(消息到达交换机)
publisher-returns: true # 退回模式(消息未到达队列)
java
@PostConstruct
public void init() {
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
log.info("消息到达交换机:{}", correlationData.getId());
} else {
log.error("消息未到达交换机,原因:{}", cause);
}
});
rabbitTemplate.setReturnsCallback(returned -> {
log.error("消息未到达队列:{},replyCode:{},replyText:{}",
returned.getMessage(), returned.getReplyCode(), returned.getReplyText());
});
}
7.2 消息持久化
java
// 队列持久化
@Bean
public Queue durableQueue() {
return QueueBuilder.durable("queue.name").build(); // durable=true
}
// 消息持久化(MessageProperties 默认持久化)
MessageProperties props = new MessageProperties();
props.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
7.3 手动确认
java
@RabbitListener(queues = "queue")
public void handle(Message message, Channel channel) {
long tag = message.getMessageProperties().getDeliveryTag();
try {
// 业务处理
process(message);
channel.basicAck(tag, false); // 确认成功
} catch (Exception e) {
if (message.getMessageProperties().getRedelivered()) {
channel.basicNack(tag, false, false); // 已重试过,丢弃
} else {
channel.basicNack(tag, false, true); // 重回队列重试
}
}
}
八、运维与管理
8.1 管理控制台
访问 http://localhost:15672 可查看:
- 连接(Connections)和通道(Channels)
- 交换机(Exchanges)和队列(Queues)
- 消息速率和堆积情况
- 用户权限管理
8.2 常用命令
bash
# 查看队列
rabbitmqctl list_queues
# 查看队列详细信息(包含消费者数)
rabbitmqctl list_queues name messages consumers
# 查看连接
rabbitmqctl list_connections
# 清除队列消息
rabbitmqctl purge_queue queue_name
8.3 集群部署
bash
# 启动多个 RabbitMQ 节点后组成集群
docker run -d --name rabbitmq1 -p 5672:5672 -p 15672:15672 rabbitmq:management
docker run -d --name rabbitmq2 -p 5673:5672 -p 15673:15672 --link rabbitmq1 rabbitmq:management
# 在 rabbitmq2 中加入集群
docker exec -it rabbitmq2 rabbitmqctl stop_app
docker exec -it rabbitmq2 rabbitmqctl join_cluster rabbit@rabbitmq1
docker exec -it rabbitmq2 rabbitmqctl start_app
九、常见问题
9.1 消息堆积
- 原因:消费者消费速度跟不上生产者
- 解决:增加消费者实例 / 调整
prefetch/ 检查业务逻辑耗时
9.2 连接超时
yaml
spring:
rabbitmq:
connection-timeout: 15000 # 连接超时
template:
reply-timeout: 5000 # 发送回复超时
9.3 消息丢失
排查链路:
生产者 -> 交换机 -> 队列 -> 消费者
① ② ③ ④
- ①:开启
publisher-confirm - ②:Exchange 必须存在(或 auto-delete 为 false)
- ③:队列持久化 + 消息持久化
- ④:手动 ACK
9.4 重复消费
消费端做好幂等性处理:
- 数据库唯一约束
- Redis 记录消息 ID(
SETNX) - 业务表内置消息 ID 字段
十、总结
| 模式 | 交换机类型 | 适用场景 |
|---|---|---|
| 简单模式 | 无(默认直连) | 点对点通信 |
| 工作队列 | 无 | 任务分发、负载均衡 |
| 发布订阅 | Fanout | 广播通知、全量推送 |
| 路由模式 | Direct | 日志分级、定向通知 |
| 主题模式 | Topic | 按规则匹配、灵活路由 |
| 延迟队列 | x-delayed-message | 订单超时取消、定时任务 |
RabbitMQ 以其稳定可靠、功能丰富、社区活跃等优势,成为业界最广泛使用的消息中间件之一。掌握 RabbitMQ 是后端开发工程师进阶的必修课。