Files

154 lines
8.3 KiB
Markdown
Raw Permalink Normal View History

2026-08-09 19:06:40 +08:00
---
tags: [test/review, mq, rabbitmq, manual-ack, dead-letter-exchange, dlq, retry-pattern, prefetch]
create time: 2026-08-09 12:00
---
# ACK 确认与死信队列_测试题
## 概述
本测试覆盖 RabbitMQ 消费者 ACK 确认机制(Auto vs Manual)、unacked 消息重放、死信交换(DLX)以及 TTL + DLX 重试队列模式。共 10 道题:6 道选择题、3 道填空题、1 道简答题。
---
## 一、选择题(6道,由浅入深)
> **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
### Q1(基础)— 考察定义层面
AMQP 协议中 Auto ACK 的本质隐患是什么?
A. Auto ACK 的性能比 Manual ACK 低很多
B. Basic.ConsumeOk 本身就是"投递动作",Auto ACK 把投递等同于处理成功,消费者崩溃后未处理消息丢失
C. Auto ACK 不支持多消费者负载均衡
D. Auto ACK 需要在消费者代码中手动调用确认方法
### Q2(基础)— 考察行为判断
当消费者处理完业务逻辑后应发送哪种 ACK 命令来永久标记消息为已消费并从队列移除?
A. basic.nack(requeue=true)
B. basic.ack
C. basic.reject
D. basic.get
### Q3(进阶)— 考察核心原理
Dead Letter Exchange(DLX)在什么情况下会捕获并重新路由消息?
A. 消费者主动调用 basic.ack 后
B. 消息 TTL 到期、被 nack(requeue=false)、或队列达到最大长度时
C. 消费者调用 basic.get 拉取消息后
D. 消息被发送到 Fanout Exchange 后
### Q4(进阶)— 考察对比辨析
以下哪种 nack 策略会一次性拒绝当前通道中所有 unacked 消息?
A. nack(tag, multiple=false, requeue=false)
B. nack(tag, multiple=true, ...)
C. ack(tag, multiple=true)
D. reject(tag, requeue=true)
### Q5(深入)— 考察场景推理
某电商系统使用三层重试队列 + DLX 方案处理第三方支付回调。第一层重试间隔 5 秒、第二层 30 秒、第三层 2 分钟。三次重试均失败后消息进入死信队列。如果希望在死信消息进入人工处理队列的同时也触发告警通知运维人员,应该如何设计?
A. 死信队列直接连接到运维人员的邮件系统
B. 死信队列配置 x-dead-letter-exchange 指向一个 monitoring-exchange,用于告警通知
C. 在消费者代码中检测死信数量然后发送邮件
D. 使用 Monitor API 轮询死信队列长度
### Q6(深入)— 考察源码级别细节
在指数退避重试的 Go 代码示例中,当处理失败时,消息被重新发布到重试 Exchange 并对原消息发送 ACK。这种设计的目的是什么?
A. 让 broker 自动决定下次投递时间
B. 避免 nack 导致的重复拉取,同时让下次投递有时间间隔
C. 将重试计数器重置为 0
D. 跳过当前消息直接处理下一条
---
## 二、填空题(3道)
### F1 — 填空
DLX 中的 `x-dead-letter-routing-key` 属性可以指定死信消息转发到 DLX 时使用的 routing key。如果未显式指定该属性,默认使用消息原来进入该队列时的 ______ 。
> **提示**: DLX 本质上是重新发布消息到指定的交换器。
### F2 — 填空
在三层重试队列模式中,每条消息只需要携带一个简单的 ______ header 而不需要维护计数器。当 TTL 到期后消息自然流入下一层队列。
> **提示**: 这个 header 控制了消息在队列中的等待时间。
### F3 — 填空
实践中常见的坑:如果消费者处理耗时过长(比如几分钟),unacked 的消息会持续占用 ______ 配额,导致其他消费者无法获得新消息。
> **提示**: 这与 QoS 预取参数相关。
---
## 三、简答题(1道)
### S1
你正在设计一个重要的订单状态更新系统,第三方平台会异步推送订单状态变更到你们的 MQ。系统具有以下特征:
- 消息处理可能有各种失败类型(网络超时、下游服务不可用、数据校验失败)
- 某些失败是暂时的(几秒后可恢复),有些是永久的(数据有问题需要人工处理)
- 消息处理必须在数据库事务完成后才确认消费
- 高峰期消费者处理速度可能跟不上生产者速度
请设计一个综合的消息处理流水线,回答以下问题:
1. Auto ACK 还是 Manual ACK?为什么?
2. nack 的 requeue 策略如何选择(临时故障 vs 不可恢复错误)?
3. 重试队列层级如何设计?
4. 如何处理背压(消费者速度 < 生产者速度)?
> **答题框架提示**:
1. ACK 模式选择及理由
2. 不同失败类型的处理策略
3. DLX + TTL 重试设计
4. prefetch 和队列长度控制
---
## 参考答案与解析
### 选择题答案
| 题号 | 正确答案 | 解析 |
|------|---------|------|
| Q1 | B | AMQP 协议层面,Basic.GetOk / Basic.ConsumeOk 本身就是"投递动作"。Auto ACK 把"投递"等同于"处理成功"。如果消费者在收到消息后尚未执行业务逻辑就宕机了,这条消息已经被认为消费完了——这就是隐患。 |
| Q2 | B | basic.ack 永久标记为已消费并从队列中移除。basic.nack(requeue=false) 走 DLX;basic.nack(requeue=true) 放回队列头;basic.reject 类似 nack 但不支持 multiple 参数。 |
| Q3 | B | DLX 在三种情况下捕获消息:basic.nack + requeue=false(被拒绝且不重入队)、TTL 到期(x-message-ttl 过期)、队列达到最大长度(x-max-length 溢出)。这些都是消息不再适合留在原队列的情况。 |
| Q4 | B | nack(tag, multiple=true, ...) 一次性拒绝当前通道中所有 unacked 消息。谨慎使用,可能造成大面积消息堆积。通常只用于通道异常关闭等特殊情况。 |
| Q5 | B | 在死信队列的配置中添加 x-dead-letter-exchange 指向 monitoring-exchange,这样最终进死信的消息会自动流转到告警队列。这利用了 DLX 的链式转发能力,是最优雅的解耦方案。A/C/D 都增加了额外的耦合或延迟。 |
| Q6 | B | 先 publish 到重试 Exchange 再 ack 原消息,这样既避免了 nack 导致的同一消息立即被同一消费者重新拉取(无限循环),又通过指数退避 delay header 让下次投递有时间间隔。这是比单纯 nack(requeue=true) 更精细的控制。 |
### 填空题答案
| 题号 | 答案 | 解析 |
|------|------|------|
| F1 | 原始 routing key | 如果没有显式指定 x-dead-letter-routing-key,DLX 会使用消息原先进入该队列时的 routing key 进行转发。这样可以保持消息的路由意图不变,简化配置。 |
| F2 | TTL(x-message-ttl) | N 层队列模式的优势在于每条消息只需携带一个简单的 TTL header 而不需要维护计数器。消息在每层队列等待对应时长后自然流入下一层,形成递增的时间链。 |
| F3 | prefetch | unacked 消息会持续占用 prefetch 配额(即 Qos 预取的槽位),导致其他消费者无法获得新消息。解决方案:适当放大 prefetch 数值、或在处理过程中定期发送 heartbeat ack。 |
### 简答题参考答案
S1:**参考答案要点**:
1. 选择 Manual ACK。原因:消息需要在数据库事务提交后才确认消费,Auto ACK 在消费者 crash 时会导致未处理消息丢失。Manual ACK 把控制权交给消费者,可以在事务提交后才发送 ack。
2. 临时故障(网络超时、下游短暂不可用)→ nack(tag, false, true) 重回队列头部或直接 re-publish 到重试 Exchange;不可恢复错误(数据校验失败、业务逻辑错误)→ nack(tag, false, false) 走 DLX 进入人工处理流程。
3. 重试设计:使用多层 TTL 队列接力。retry-queue-1(TTL 5s,DLX→retry-queue-2)→ retry-queue-2(TTL 30s,DLX→retry-queue-3)→ retry-queue-3(TTL 120s,DLX→dead-letter-queue)。也可在消息 header 中嵌入 retryCount 字段配合指数退避。
4. 背压处理:设置合理的 prefetch_count(通用场景 10~50),启用队列最大长度限制(x-max-length)防止无限堆积。当队列满时新消息被拒并走 DLX。监控 unacked 数量和 queue depth 发现趋势性增长立即扩容消费者。
**评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖 ACK 选择、nack 策略、重试设计和背压处理四个维度。
## 关联笔记
- [[04.MQ/rabbitmq/消息持久化与可靠性投递]]
- [[04.MQ/rabbitmq/Exchange 路由机制]]
- [[04.MQ/rabbitmq/推拉结合消费模式]]