RabbitMQ 重试机制:Auto 与 Manual 消费模式
在 RabbitMQ 消费端,消息消费失败后的重试策略 直接影响系统的可靠性。Spring AMQP 提供了两种消费模式:Auto 模式(自动确认) 和 Manual 模式(手动确认)。
本文详细对比两种模式的差异,重点分析 deliveryTag 的变化规律。
一、核心区别:Tag 的变化
| 模式 | 重试时 Tag 变化 | 原因 |
|---|---|---|
| Auto 模式 | Tag 保持不变 | 内部重试,同一条消息 |
| Manual 模式(入队) | Tag 持续增加 | 重新入队 = 新消息,产生新 Tag |
关键理解:
- Auto 模式重试是同一条消息在内部重试,Tag 不变
- Manual 模式
basicNack(requeue=true)是消息重新入队,相当于新消息,Tag 递增
二、Auto 模式(自动确认)
2.1 配置
yaml
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: auto
retry:
enabled: true
max-attempts: 3 # 最大重试次数
initial-interval: 1000 # 首次重试间隔(毫秒)
multiplier: 2 # 间隔倍数
2.2 Tag 变化示例
消息到达(Tag=1)→ 消费失败 → 重试(Tag=1)→ 消费失败 → 重试(Tag=1)→ 消费成功
↑ ↑ ↑
Tag=1 Tag=1 Tag=1
同一条消息 同一条消息 同一条消息
2.3 代码示例
java
@RabbitListener(queues = "test-queue")
public void onMessage(String message) {
System.out.println("收到消息: " + message);
// 消费失败,自动重试
// Tag 保持不变,始终是同一条消息
throw new RuntimeException("消费失败");
}
2.4 Auto 模式特点
| 特点 | 说明 |
|---|---|
| Tag 不变 | 重试时 Tag 保持不变,始终是同一条消息 |
| 内部重试 | Spring 内部处理,不经过队列 |
| 自动入队 | 重试次数耗尽后,消息重新入队 |
| 简单易用 | 代码简洁,无需手动管理 |
三、Manual 模式(手动确认)
3.1 配置
yaml
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual
prefetch: 1
3.2 Tag 变化示例(入队)
消息到达(Tag=1)→ 消费失败 → basicNack(requeue=true) → 重新入队
↓
消息到达(Tag=2)← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ← ←
↑
Tag=2(新消息)
3.3 代码示例
java
@RabbitListener(queues = "test-queue")
public void onMessage(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
System.out.println("当前 Tag: " + deliveryTag);
try {
// 业务处理...
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
// 重新入队:Tag 会增加(变成新消息)
channel.basicNack(deliveryTag, false, true); // requeue=true
}
}
3.4 Manual 模式三种响应方式
| 方法 | requeue 参数 | Tag 变化 | 说明 |
|---|---|---|---|
basicAck |
- | 消息删除 | 确认消费 |
basicNack(requeue=true) |
true | Tag +1 | 重新入队,变成新消息 |
basicNack(requeue=false) |
false | 消息删除 | 丢弃或进入死信队列 |
四、Tag 变化对比
4.1 完整对比表
| 场景 | Auto 模式 | Manual 模式(入队) |
|---|---|---|
| 首次消费 | Tag=1 | Tag=1 |
| 第1次重试 | Tag=1(不变) | Tag=2(+1) |
| 第2次重试 | Tag=1(不变) | Tag=3(+1) |
| 第3次重试 | Tag=1(不变) | Tag=4(+1) |
| 消息本质 | 同一条消息 | 每次都是新消息 |
4.2 流程对比图
Auto 模式(Tag 不变):
┌─────────────────────────────────┐
│ 内部重试 │
│ Tag=1 → Tag=1 → Tag=1 │
│ (同一条消息) │
消息(Tag=1)→ 消费 → 失败 → 重试 → 失败 → 重试 → 成功/入队
Manual 模式(Tag 递增):
消息(Tag=1)→ 消费 → 失败 → basicNack(requeue=true)
↓
重新入队(新消息)
↓
消息(Tag=2)→ 消费 → 失败 → basicNack(requeue=true)
↓
重新入队(新消息)
↓
消息(Tag=3)→ 消费 → 成功 → basicAck
五、为什么 Tag 会变化?
5.1 Auto 模式原理
消费者 ← → Spring 内部队列(内存)
↓
失败后内部重试
↓
Tag 不变(同一条消息)
Auto 模式的重试发生在 Spring 内部,消息从未离开过消费者,所以 Tag 保持不变。
5.2 Manual 模式原理
消费者 ← → RabbitMQ Broker
↓
basicNack(requeue=true)
↓
消息回到 Broker 队列
↓
重新投递(新消息,新 Tag)
Manual 模式 basicNack(requeue=true) 是将消息重新放回 Broker,RabbitMQ 会将其视为新消息,分配新的 deliveryTag。
六、注意事项
-
Manual 入队会导致 Tag 递增 :每次
basicNack(requeue=true)都会产生新 Tag,可能影响依赖 Tag 的业务逻辑。 -
Auto 重试是内部机制:Spring 内部重试不经过 Broker,Tag 不变,但重试次数有限。
-
幂等性:无论哪种模式,都要保证消费逻辑的幂等性。
-
死信队列:Manual 不入队时,建议配置死信队列,避免消息丢失。
-
性能:Auto 模式性能高于 Manual 模式。