RabbitMQ 知识点全面总结:核心模型、可靠性、顺序性、监控与死信队列
一、RabbitMQ 核心概念
RabbitMQ 是基于 AMQP(高级消息队列协议)的开源消息中间件,支持多种消息模型,具备高可靠、可扩展、易集成等特性。其核心组件包括:
- Broker:消息服务器(如安装在本地的 localhost)
- Exchange:消息交换器(负责路由消息到队列)
- Queue:消息队列(存储消息的缓冲区)
- Binding:绑定关系(Exchange 与 Queue 的关联规则)
- Producer:消息生产者(发送消息)
- Consumer:消息消费者(接收消息)
二、RabbitMQ 五种核心消息模型
RabbitMQ 支持 5 种基础消息模型,本质是通过 Exchange 的不同类型实现的。以下是模型示意图及说明:
2.1 简单队列(Simple Queue)
特点:单生产者 → 单消费者,一对一通信。
适用场景:简单的任务分发(如单实例日志收集)。
Java 代码示例(Spring Boot):
// 生产者
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendSimpleMsg(String msg) {
rabbitTemplate.convertAndSend("simpleQueue", msg); // 直接发送到队列
}
// 消费者
@RabbitListener(queues = "simpleQueue")
public void receiveSimpleMsg(String msg) {
System.out.println("接收简单队列消息:" + msg);
}
2.2 工作队列(Work Queue)
特点:单生产者 → 多消费者,竞争消费同一消息(默认轮询分发)。
优化:可通过 fair dispatch(公平分发)避免消费者负载不均(设置 spring.rabbitmq.listener.simple.prefetch=1)。
适用场景:任务削峰(如批量订单处理)。
# application.properties 配置公平分发(预取计数=1)
spring.rabbitmq.listener.simple.prefetch=1
2.3 发布-订阅(Publish/Subscribe)
特点:单生产者 → 多消费者(通过 FanoutExchange 广播消息到所有绑定的队列)。
适用场景:事件广播(如系统通知、实时推送)。
// 声明 FanoutExchange(需在配置类中定义)
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("pubSubExchange");
}
// 生产者发送消息到 Exchange
public void sendPubSubMsg(String msg) {
rabbitTemplate.convertAndSend("pubSubExchange", "", msg); // 路由键为空(Fanout 忽略)
}
// 消费者 A 绑定队列 1
@RabbitListener(queues = "pubSubQueue1")
public void receivePubSub1(String msg) {
System.out.println("消费者1接收:" + msg);
}
// 消费者 B 绑定队列 2
@RabbitListener(queues = "pubSubQueue2")
public void receivePubSub2(String msg) {
System.out.println("消费者2接收:" + msg);
}
2.4 路由(Routing)
特点:生产者 → DirectExchange(根据路由键精确匹配)→ 绑定队列。
适用场景:条件路由(如按日志级别分发:ERROR、INFO)。
// 声明 DirectExchange
@Bean
public DirectExchange directExchange() {
return new DirectExchange("routingExchange");
}
// 生产者发送带路由键的消息
public void sendRoutingMsg(String routingKey, String msg) {
rabbitTemplate.convertAndSend("routingExchange", routingKey, msg);
}
// 消费者绑定 ERROR 路由键的队列
@RabbitListener(queues = "errorQueue")
public void receiveError(String msg) {
System.out.println("接收 ERROR 消息:" + msg);
}
// 消费者绑定 INFO 路由键的队列
@RabbitListener(queues = "infoQueue")
public void receiveInfo(String msg) {
System.out.println("接收 INFO 消息:" + msg);
}
2.5 主题(Topics)
特点:生产者 → TopicExchange(根据路由键通配符匹配)→ 绑定队列。
通配符规则:*(匹配一个单词)、#(匹配零或多个单词)。
适用场景:复杂条件路由(如按用户类型+操作类型分发)。
// 声明 TopicExchange
@Bean
public TopicExchange topicExchange() {
return new TopicExchange("topicExchange");
}
// 生产者发送带通配符路由键的消息(如 "user.create"、"order.pay.*")
public void sendTopicMsg(String routingKey, String msg) {
rabbitTemplate.convertAndSend("topicExchange", routingKey, msg);
}
// 消费者绑定 "user.#" 匹配所有 user 相关消息
@RabbitListener(queues = "userQueue")
public void receiveUser(String msg) {
System.out.println("接收 USER 消息:" + msg);
}
// 消费者绑定 "order.pay.success" 精确匹配
@RabbitListener(queues = "orderQueue")
public void receiveOrder(String msg) {
System.out.println("接收 ORDER 消息:" + msg);
}
三、如何保证消息不丢失?
消息丢失可能发生在 生产者 → Broker → 消费者 三个阶段,需分别处理:
3.1 生产者阶段:确认机制(Confirm)
通过 ConfirmCallback 确认消息是否到达 Broker。
配置步骤:
- 开启生产者确认:spring.rabbitmq.publisher-confirm-type=correlated(关联回调)。
- 实现 RabbitTemplate.ConfirmCallback 接口。
@Component
public class RabbitMQProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
// 生产者确认回调
@PostConstruct
public void init() {
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
System.out.println("消息成功到达 Broker");
} else {
System.err.println("消息未到达 Broker,原因:" + cause);
// 可在此重发消息
}
});
}
public void sendMessage(String queue, String msg) {
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend(queue, msg, correlationData);
}
}
3.2 Broker 阶段:持久化(Persistence)
通过持久化保证 Broker 重启后消息不丢失。
配置方式:
- 队列持久化:durable=true(声明队列时设置)。
- 消息持久化:MessageProperties.PERSISTENT_TEXT_PLAIN(发送消息时设置)
// 声明持久化队列(在配置类中)
@Bean
public Queue durableQueue() {
return new Queue("durableQueue", true); // 第二个参数为持久化标志
}
// 发送持久化消息
public void sendDurableMsg(String msg) {
rabbitTemplate.convertAndSend("durableQueue", msg, message -> {
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 设置消息持久化
return message;
});
}
3.3 消费者阶段:手动确认(ACK)
通过 manual ack 避免因消费者崩溃导致消息丢失。
配置方式:
- 开启手动确认:spring.rabbitmq.listener.simple.acknowledge-mode=manual。
- 消费者处理完成后调用 channel.basicAck(deliveryTag, false)。
@RabbitListener(queues = "manualAckQueue")
public void receiveManualAck(String msg, Channel channel, Message message) throws IOException {
try {
System.out.println("处理消息:" + msg);
// 模拟业务处理耗时
Thread.sleep(1000);
// 手动确认(第二个参数为是否批量确认)
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
// 处理失败,拒绝消息(可重新入队或丢弃)
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); // 第三个参数为是否重新入队
}
}
四、如何避免消息重复消费?
消息重复消费的根本原因是:Broker 在未收到消费者 ACK 时,会将消息重新投递(如消费者崩溃或超时)。
解决方案:在消费者端实现 幂等性(多次消费同一消息结果与一次消费一致)。
4.1 幂等性实现策略
| 策略 | 说明 |
|---|---|
| 唯一 ID 校验 | 为每条消息生成唯一 ID(如 UUID),消费者记录已处理的 ID(Redis/数据库) |
| 数据库唯一索引 | 插入数据时使用唯一索引,避免重复写入 |
| Redis 缓存标记 | 消费前检查 Redis 中是否存在该消息 ID,存在则跳过 |
4.2 Java 代码示例(Redis 去重)
@RabbitListener(queues = "idempotentQueue")
public void receiveIdempotentMsg(String msg, Channel channel, Message message) throws IOException {
String messageId = extractMessageId(msg); // 从消息中提取唯一 ID(如 JSON 中的 "id" 字段)
// 检查 Redis 中是否已处理
if (redisTemplate.opsForValue().setIfAbsent("msg:" + messageId, "1", 24, TimeUnit.HOURS)) {
// 未处理过,执行业务逻辑
processMsg(msg);
// 手动确认
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} else {
// 已处理过,直接确认(不重复消费)
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}
}
private String extractMessageId(String msg) {
// 假设消息是 JSON 格式,解析出 "id" 字段
JSONObject json = JSON.parseObject(msg);
return json.getString("id");
}
五、如何保证消息顺序性?
RabbitMQ 默认不保证消息顺序(因负载均衡可能将消息分发到不同队列)。
保证顺序的方案:
- 单队列单消费者:所有消息进入同一队列,由单个消费者顺序处理(牺牲吞吐量)。
- 消息分区:通过相同路由键将消息发送到同一队列(如订单 ID 作为路由键)
5.1 单队列单消费者示例
// 生产者发送消息时使用相同路由键(如 "order")
public void sendOrderMsg(String orderId, String msg) {
rabbitTemplate.convertAndSend("orderExchange", "order", msg); // 路由键固定为 "order"
}
// 消费者使用单线程处理(@RabbitListener 默认是多线程,需配置为单线程)
@RabbitListener(queues = "orderQueue", concurrency = "1") // 并发数=1(单线程)
public void receiveOrderMsg(String msg) {
System.out.println("顺序处理订单消息:" + msg);
}
六、RabbitMQ 监控
RabbitMQ 提供内置的管理插件(rabbitmq-management),可通过 Web 界面监控集群状态。
6.1 启用管理插件
# 进入 RabbitMQ 安装目录
rabbitmq-plugins enable rabbitmq_management
启动后访问 http://localhost:15672(默认账号:guest/guest,仅限本地访问)。
6.2 监控关键指标
| 指标 | 说明 |
|---|---|
| Queues | 队列数量、消息总数、消费者数量 |
| Connections | 客户端连接数、通道数 |
| Exchanges | 交换器类型、绑定数量 |
| Message Rates | 消息生产/消费速率(msg/s) |
| Disk/Memory Usage | Broker 磁盘/内存使用情况 |
七、死信队列(Dead Letter Queue)
7.1 死信形成原因
消息变为死信(Dead Letter)的三种场景:
- 消息过期(x-message-ttl 设置超时时间)。
- 消费者拒绝(basicNack/basicReject 且 requeue=false)。
- 队列满(x-max-length 设置队列最大长度)。
7.2 死信队列配置步骤
- 声明死信交换器(DLX Exchange)和死信队列(DLQ)。
- 原队列绑定死信交换器,并设置死信参数。
java
// 声明死信交换器和队列(配置类)
@Bean
public DirectExchange dlxExchange() {
return new DirectExchange("dlxExchange");
}
@Bean
public Queue dlq() {
return new Queue("dlq");
}
@Bean
public Binding dlqBinding() {
return BindingBuilder.bind(dlq()).to(dlxExchange()).with("dlqRoutingKey");
}
// 声明原队列(绑定死信参数)
@Bean
public Queue originalQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", "dlxExchange"); // 死信交换器
args.put("x-dead-letter-routing-key", "dlqRoutingKey"); // 死信路由键
args.put("x-message-ttl", 10000); // 消息过期时间(10秒)
args.put("x-max-length", 100); // 队列最大长度(100条)
return new Queue("originalQueue", true, false, false, args);
}
// 生产者发送消息到原队列
public void sendToOriginalQueue(String msg) {
rabbitTemplate.convertAndSend("originalQueue", msg);
}
// 消费者监听死信队列(处理死信)
@RabbitListener(queues = "dlq")
public void receiveDlqMsg(String msg) {
System.out.println("处理死信消息:" + msg);
}
总结
RabbitMQ 是功能强大的消息中间件,核心能力覆盖多种消息模型、高可靠性、顺序性保证等。实际使用中需根据业务场景选择模型(如发布订阅用于广播,路由用于条件分发),并通过确认机制、持久化、幂等性设计保障消息可靠性。监控和死信队列则是运维和故障排查的关键工具。

浙公网安备 33010602011771号