在 Apache Pulsar 中,消息确认(Acknowledgment)与重试机制 是保障消息 可靠处理、避免丢失、支持容错 的核心能力。掌握 ack、nack、自动重试、死信队列等机制,是构建高可用、健壮消息系统的必备技能。
📘 Pulsar 消息确认与重试详解
一、消息确认机制(Acknowledgment)
Pulsar 要求消费者显式确认消息已成功处理,否则 Broker 会认为消息未完成,可能重新投递。
1. 两种确认方式
| 类型 | 方法 | 说明 |
|---|---|---|
| ✅ Individual Ack(单条确认) | consumer.acknowledge(msg) | 确认某一条消息 |
| ✅ Cumulative Ack(累积确认) | consumer.acknowledgeCumulatively(msg) | 确认当前消息及之前所有未确认消息 |
⚠️ 仅适用于 Shared 订阅模式以外的模式(即 Exclusive / Failover)
📌 语义对比
| 模式 | Individual Ack | Cumulative Ack |
|---|---|---|
| Exclusive / Failover | ✅ 支持 | ✅ 支持 |
| Shared / Key_Shared | ✅ 支持 | ❌ 不支持(无序) |
示例(Exclusive 模式):
Message msg1 = consumer.receive();
Message msg2 = consumer.receive();
Message msg3 = consumer.receive();
consumer.acknowledge(msg2); // 只确认 msg2
consumer.acknowledgeCumulatively(msg2); // 确认 msg1 和 msg2
💡 Cumulative Ack 类似于 TCP 的“累计确认”,提升性能,但粒度较粗。
📊 性能影响对比
| 特性 | Individual Ack | Cumulative Ack |
|---|---|---|
| 网络开销 | 高(每条消息一个 ACK) | 低(批量确认) |
| 精确性 | 高(可跳过确认) | 低(必须按序) |
| 重试粒度 | 单条 | 从第一个未确认开始 |
| 适用场景 | 处理顺序不确定、需精细控制 | 顺序处理、高吞吐 |
✅ 推荐:顺序处理用
cumulative,并行处理用individual。
二、否定确认(Negative Acknowledgment, NACK)
当消息处理失败时,消费者可通过 nack 告诉 Broker 立即重新投递该消息,而不是等待超时。
1. 使用方式
try {
process(msg);
consumer.acknowledge(msg);
} catch (Exception e) {
consumer.negativeAcknowledge(msg); // 立即重试
}
2. NACK 的行为
- 消息被立即放入重试队列(不会等待
ackTimeout)。 - Broker 尽快将消息重新投递给同一个或另一个消费者。
- 适用于瞬时错误(如数据库连接失败、依赖服务超时)。
3. 配置 NACK 重试延迟
.consumer.negativeAckRedeliveryDelay(5, TimeUnit.SECONDS)
控制 NACK 后消息重新投递的最小延迟,防止“消息风暴”。
三、Acknowledgment Timeout(Ack 超时)
仅适用于 Exclusive 和 Failover 订阅模式。
1. 什么是 Ack Timeout?
- 如果消费者在指定时间内未对消息调用
ack,Broker 会自动重发该消息。 - 用于防止消费者处理消息后忘记确认,或处理卡住。
ackTimeout(30, TimeUnit.SECONDS)
2. 工作流程
Consumer.receive()
↓
处理中...(30秒内未 ack)
↓
Broker 检测超时
↓
消息重新投递 → 可能被同一或另一消费者接收
⚠️ 风险:可能造成重复处理,需业务侧保证幂等性。
3. 与 NACK 的区别
| 特性 | Ack Timeout | NACK |
|---|---|---|
| 触发方式 | 自动(超时) | 手动(代码调用) |
| 延迟 | 固定时间 | 可配置 negativeAckRedeliveryDelay |
| 适用模式 | Exclusive / Failover | 所有模式 |
| 是否立即重试 | 否(等待超时) | 是(立即) |
✅ 最佳实践:优先使用 NACK,
ackTimeout作为兜底。
四、自动重试机制(Retry with retryLetterTopic)
Pulsar 支持基于 重试 Topic(Retry Letter Topic) 的自动重试机制,适用于 Shared 和 Key_Shared 模式(这些模式不支持 ackTimeout)。
1. 核心组件
retryLetterTopic:临时存储待重试的消息。deadLetterTopic:最终无法处理的消息转入死信队列。maxRedeliverCount:最大重试次数。
2. 配置方式(Java)
Consumer<String> consumer = client.newConsumer(Schema.STRING)
.topic("orders")
.subscriptionName("order-processor")
.enableRetry(true) // 启用重试机制
.deadLetterPolicy(DeadLetterPolicy.builder()
.maxRedeliverCount(5)
.retryLetterTopic("persistent://mycompany/retry/order-retry")
.deadLetterTopic("persistent://mycompany/dlq/order-dlq")
.build())
.subscribe();
3. 工作流程
Producer → Topic
↓
Consumer.receive() → 处理失败
↓
consumer.negativeAcknowledge(msg)
↓
Broker 将消息写入 retryLetterTopic(带延迟)
↓
延迟到期 → 消息重新投递到原 Topic
↓
重复最多 maxRedeliverCount 次
↓
超过次数 → 转入 deadLetterTopic
✅ 支持指数退避重试(需自定义函数)。
4. CLI 配置(pulsar-admin)
pulsar-admin namespaces set-dead-letter-policy my-tenant/my-namespace \
--maxRedeliverCount 5 \
--deadLetterTopic my-dlq-topic \
--retryLetterTopic my-retry-topic
五、死信队列(Dead Letter Topic, DLQ)
当消息重试达到上限后,自动转入 DLQ,便于后续人工分析或补偿。
1. DLQ 的用途
- 存储“顽固失败”消息。
- 避免阻塞正常消费流。
- 支持离线分析、重放、报警。
2. 消费 DLQ 消息
Consumer<byte[]> dlqConsumer = client.newConsumer()
.topic("persistent://mycompany/dlq/order-dlq")
.subscriptionName("dlq-processor")
.subscribe();
Message<byte[]> msg = dlqConsumer.receive();
// 分析失败原因,触发告警或人工处理
六、四种订阅模式下的确认与重试行为对比
| 订阅模式 | 支持 Cumulative Ack | 支持 Ack Timeout | 支持 NACK | 支持 retryLetterTopic | 典型重试机制 |
|---|---|---|---|---|---|
| Exclusive | ✅ | ✅ | ✅ | ⚠️ 可用但不推荐 | Ack Timeout + NACK |
| Failover | ✅ | ✅ | ✅ | ⚠️ 可用但不推荐 | Ack Timeout + NACK |
| Shared | ❌ | ❌ | ✅ | ✅ | retryLetterTopic + NACK |
| Key_Shared | ❌ | ❌ | ✅ | ✅ | retryLetterTopic + NACK |
📌 关键结论:
- Shared / Key_Shared:必须启用
enableRetry(true)才能实现自动延迟重试。- Exclusive / Failover:可使用
ackTimeout或 NACK,但无内置延迟重试(需手动实现)。
七、最佳实践建议
| 实践 | 建议 |
|---|---|
| ✅ 所有业务处理必须显式 ack/nack | 防止消息堆积或丢失 |
| ✅ Shared 模式必须启用 retryLetterTopic | 否则失败消息无法自动重试 |
| ✅ 设置合理的 maxRedeliverCount | 避免无限重试 |
| ✅ DLQ 必须有消费者监控 | 及时发现异常 |
| ✅ 业务逻辑保证幂等性 | 防止重复处理 |
| ✅ NACK 优于 ackTimeout | 更快发现问题 |
| ✅ 避免频繁 NACK | 防止消息风暴,配合 negativeAckRedeliveryDelay |
| ✅ 使用 Schema + Validation | 减少因格式错误导致的失败 |
八、可视化:重试与 DLQ 流程图(文字描述)
+------------------+
| Original Topic |
+------------------+
|
receive() → 处理失败
|
v
+----------------------+
| consumer.nack(msg) |
+----------------------+
|
+-----------------------+------------------------+
| |
v (Shared / Key_Shared) v (Exclusive / Failover)
+------------------------------+ +---------------------------+
| retryLetterTopic (延迟重试) | | 等待 ackTimeout 超时 |
| 延迟后重新投递 | | 超时后重发 |
+------------------------------+ +---------------------------+
| |
v (重试次数 < max) v
重新投递 → 原 Topic 重新投递 → 原消费者
| |
+-------------------+----------------------------+
|
重试次数 ≥ maxRedeliverCount?
|
YES
|
v
+---------------------+
| deadLetterTopic (DLQ) |
| 人工或自动处理 |
+---------------------+
✅ 总结
| 机制 | 适用场景 | 推荐度 |
|---|---|---|
| Individual Ack | 并行处理、精细控制 | ⭐⭐⭐⭐ |
| Cumulative Ack | 顺序处理、高吞吐 | ⭐⭐⭐⭐ |
| NACK | 所有模式,立即重试 | ⭐⭐⭐⭐⭐ |
| Ack Timeout | Exclusive/Failover,兜底 | ⭐⭐⭐ |
| retryLetterTopic | Shared/Key_Shared,延迟重试 | ⭐⭐⭐⭐⭐ |
| deadLetterTopic | 所有场景,最终兜底 | ⭐⭐⭐⭐⭐ |
📌 一句话总结:
ack是承诺,nack是求助,retryLetterTopic是智能重试,DLQ是最后防线 —— 合理组合这些机制,才能构建真正可靠的消息消费系统。
1062

被折叠的 条评论
为什么被折叠?



