154 lines
8.4 KiB
Markdown
154 lines
8.4 KiB
Markdown
|
|
---
|
|||
|
|
tags: [test/review, mq, rabbitmq, pull-model, qos, prefetch, backpressure, consumer-balance]
|
|||
|
|
create time: 2026-08-09 12:00
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 推拉结合消费模式_测试题
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
本测试覆盖 RabbitMQ 消费模型的本质(Push vs Pull 混合理解)、QoS 预取策略、背压处理和消费者负载均衡。共 10 道题:6 道选择题、3 道填空题、1 道简答题。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、选择题(6道,由浅入深)
|
|||
|
|
|
|||
|
|
> **难度阶梯**: Q1-Q2 基础概念 → Q3-Q4 核心原理 → Q5-Q6 深入应用/边界场景
|
|||
|
|
|
|||
|
|
### Q1(基础)— 考察定义层面
|
|||
|
|
|
|||
|
|
RabbitMQ Consumer API 名为 `basic.consume`(拉取)但实际运行模型是 Server Push(服务端推送)。以下说法正确的是:
|
|||
|
|
|
|||
|
|
A. `basic.consume` 和 `basic.get` 的行为完全相同
|
|||
|
|
B. `basic.consume` 建立订阅后 Broker 持续主动投递,`basic.get` 每次调用阻塞获取单条消息
|
|||
|
|
C. `basic.consume` 只能获取一次就断开连接
|
|||
|
|
D. `basic.get` 是默认的生产者发送方法
|
|||
|
|
|
|||
|
|
### Q2(基础)— 考察行为判断
|
|||
|
|
|
|||
|
|
设置 `prefetch_count = 1` 可以实现公平分发(Fair Dispatch),其核心原因是:
|
|||
|
|
|
|||
|
|
A. Broker 会随机选择消费者分配消息
|
|||
|
|
B. A 每处理完一条并发 ack 后才收到下一条,自然形成速度匹配
|
|||
|
|
C. prefetch=1 时所有消息排队等候直到第一个消费者空闲
|
|||
|
|
D. prefetch=1 强制只有一个消费者活跃
|
|||
|
|
|
|||
|
|
### Q3(进阶)— 考察核心原理
|
|||
|
|
|
|||
|
|
`global=true` 是 Prefetch 设置中的一个遗留参数,以下哪一项描述了在多 Channel 场景下使用 global=true 可能导致的意外行为?
|
|||
|
|
|
|||
|
|
A. prefetch 只对单个 Channel 生效,不会跨 Channel 共享配额
|
|||
|
|
B. prefetch 对整个连接(而非单个 Channel)生效,导致一个 Channel 消耗了所有配额时其他 Channel 无法获得新消息
|
|||
|
|
C. global=true 会使 prefetch_count 失效
|
|||
|
|
D. global=true 只在 Fanout Exchange 中有效
|
|||
|
|
|
|||
|
|
### Q4(进阶)— 考察对比辨析
|
|||
|
|
|
|||
|
|
以下哪种预取值范围最适合通用生产环境?
|
|||
|
|
|
|||
|
|
A. prefetch=1
|
|||
|
|
B. prefetch=0
|
|||
|
|
C. prefetch=10~50
|
|||
|
|
D. prefetch=10000+
|
|||
|
|
|
|||
|
|
### Q5(深入)— 考察场景推理
|
|||
|
|
|
|||
|
|
一个日终跑批系统需要从数据库全量读取商品信息并通过 MQ 推送到缓存集群预热。数据量大但每条消息处理简单。同时另一个即时订单状态推送系统要求低延迟。这两个场景分别适合的 prefetch 值是:
|
|||
|
|
|
|||
|
|
A. 两者都用 prefetch=1
|
|||
|
|
B. 跑批用 prefetch=200,订单推送用 prefetch=3
|
|||
|
|
C. 跑批用 prefetch=3,订单推送用 prefetch=200
|
|||
|
|
D. 两者都用 prefetch=0(无限)
|
|||
|
|
|
|||
|
|
### Q6(深入)— 考察源码级别细节
|
|||
|
|
|
|||
|
|
在 `basic.consume` 建立的流控窗口机制中,Window 耗尽时 Broker 会发生什么?
|
|||
|
|
|
|||
|
|
A. Broker 立即关闭 TCP 连接
|
|||
|
|
B. Broker 暂停向该 Channel 投递直到窗口回收(ack 释放槽位)
|
|||
|
|
C. Broker 将多余消息转移到其他 Channel
|
|||
|
|
D. Broker 丢弃超出的消息
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、填空题(3道)
|
|||
|
|
|
|||
|
|
### F1 — 填空
|
|||
|
|
|
|||
|
|
设 prefetch = N 时,Broker 可以在没有收到 ack 的情况下最多向该 Channel 发送 ______ 条消息累积在未确认状态。当 ack 一条消息后 unacked 数减一,Broker 再补发一条。
|
|||
|
|
|
|||
|
|
> **提示**: 这就是 prefetch 值本身的含义。
|
|||
|
|
|
|||
|
|
### F2 — 填空
|
|||
|
|
|
|||
|
|
同一个 Queue 可以有多个消费者构成 Consumer Group,RabbitMQ 以 ______ 方式将消息均匀分配给各消费者。但在 prefetch=1 时可能出现只有一个消费者活跃而另一个空等的情况。
|
|||
|
|
|
|||
|
|
> **提示**: 这种分发方式的英文术语是什么?
|
|||
|
|
|
|||
|
|
### F3 — 填空
|
|||
|
|
|
|||
|
|
根据调优黄金法则:先设 prefetch=1 观察各消费者的处理耗时分布,再根据 p99 耗时最高的那个消费者来估算合适的 batch size,初始设为预估值的 ______ 倍即可。
|
|||
|
|
|
|||
|
|
> **提示**: 回顾文档中的调优经验。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、简答题(1道)
|
|||
|
|
|
|||
|
|
### S1
|
|||
|
|
|
|||
|
|
你正在为一家在线教育公司设计课程点播系统的消息处理 pipeline。系统有以下特征:
|
|||
|
|
- 用户观看视频后会触发埋点事件(播放进度、点赞、评论),每秒约 5000 条
|
|||
|
|
- 埋点数据处理流程:去重 → 聚合 → 写入时序数据库
|
|||
|
|
- 去重操作涉及 Redis 分布式锁,耗时约 50ms
|
|||
|
|
- 聚合操作相对较快(约 5ms)
|
|||
|
|
- 写入时序数据库是 IO 密集型操作(约 20ms)
|
|||
|
|
- 高峰期总处理耗时约 75ms/条消息
|
|||
|
|
- 当前部署了 5 个消费者实例
|
|||
|
|
|
|||
|
|
请设计 QoS 配置方案并回答:
|
|||
|
|
1. 整体 prefetch 值应该设置多少?为什么?
|
|||
|
|
2. 是否需要对不同阶段的处理进行拆分(多队列串联)?
|
|||
|
|
3. prefetch 过大或过小各自会带来什么问题?
|
|||
|
|
|
|||
|
|
> **答题框架提示**:
|
|||
|
|
1. 基于单条消息处理耗时计算合适的 prefetch
|
|||
|
|
2. 考虑是否需要将耗时长的阶段拆出独立队列
|
|||
|
|
3. 大 prefetch 和小 prefetch 的权衡分析
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 参考答案与解析
|
|||
|
|
|
|||
|
|
### 选择题答案
|
|||
|
|
|
|||
|
|
| 题号 | 正确答案 | 解析 |
|
|||
|
|
|------|---------|------|
|
|||
|
|
| Q1 | B | `basic.consume` 建立订阅后 Broker 持续主动投递(Push),适用于大多数场景。`basic.get` 每次调用阻塞获取单条消息(Pull),适用于管理界面和健康检查等非实时场景。API 名为 consume 但实质是 Push,源于 AMQP 协议早期约定。 |
|
|||
|
|
| Q2 | B | prefetch=1 实现 Fair Dispatch 的核心机制:A 每处理完一条、发一个 ack,才会收到下一条,自然形成速度匹配。如果 prefetch=N(大数值),Broker 可能在短时间内把 N 条消息全发给 A 而 B 空闲等待。 |
|
|||
|
|
| Q3 | B | global=true 是对整个连接(而非单个 Channel)生效,在多 Channel 场景下会导致意外行为。现代客户端中一律设为 false,作用于单个 Channel。这是遗留参数,不建议使用。 |
|
|||
|
|
| Q4 | C | prefetch=10~50 是通用生产环境默认值。prefetch=1 吞吐量较低适合处理耗时差异大的场景;prefetch=100~500 吞吐最高但内存压力大;prefetch=0(不设置)为无限值严禁在生产中使用。 |
|
|||
|
|
| Q5 | B | 跑批场景:数据量大但处理简单,适合高吞吐配置 prefetch=200 配合批量更新 Redis pipeline。订单推送:latency 优先级高于 throughput,设置 prefetch=3 让消费者尽快处理完再拿下一条。 |
|
|||
|
|
| Q6 | B | 流控窗口耗尽时,Broker 暂停向该 Channel 投递直到窗口回收。这是内建的背压机制——消费者发送 basic.ack 后窗口恢复,或者 window refill 后继续投递。不会丢消息也不会断连。 |
|
|||
|
|
|
|||
|
|
### 填空题答案
|
|||
|
|
|
|||
|
|
| 题号 | 答案 | 解析 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| F1 | N | prefetch_count 定义了单个 Channel 上允许的最大未确认消息数。这 N 条消息累积在 unacked 状态,ack 一条后 Broker 再补发一条。prefetch 越大意味着降低网络 RTT 开销但也增加了内存压力和崩溃损失量。 |
|
|||
|
|
| F2 | Round-Robin(轮询) | RabbitMQ 以轮询方式将消息平均分配给同一 Queue 的各消费者。但 Round-Robin 不一定是公平的——处理快的消费者实际上承担更多工作量,这正是 prefetch=1 要解决的问题。 |
|
|||
|
|
| F3 | 2~3 | 调优黄金法则:先设 prefetch=1 观察各消费者的处理耗时分布,再根据 p99 耗时最高的消费者估算合适的 batch size,初始设为预估值的 2~3 倍。之后通过监控 unacked 数量和 queue depth 微调。 |
|
|||
|
|
|
|||
|
|
### 简答题参考答案
|
|||
|
|
|
|||
|
|
S1:**参考答案要点**:
|
|||
|
|
1. 单条消息总处理耗时约 75ms,5 个消费者实例理论上可支撑 5 / 75ms ≈ 66 QPS。但实际需求为 5000 QPS,远超出单节点处理能力。prefetch 建议设置为 10~20:太小无法满足吞吐需求,太大会导致消息积压在消费者内存中。初始可按 prefetched messages ≤ 处理耗时 × prefetch_count 来估算。
|
|||
|
|
2. 需要拆分!去重步骤涉及 Redis 分布式锁是串行瓶颈(50ms),应将去重作为独立的第一层消费者队列(快速筛选有效请求),聚合和写库放到第二层队列做批处理。这样可以大幅缓解单机瓶颈。
|
|||
|
|
3. prefetch 过大的问题:Broker 可以积压更多消息到消费者内存,降低每次投递的往返开销但也增加了内存压力、崩溃恢复时的损失量以及处理时间过长导致 unacked 消息占满 prefetch 配额的风险。prefetch 过小的问题:串行节奏限制吞吐量,网络 RTT 开销占比增大。
|
|||
|
|
|
|||
|
|
**评分标准**:答出任意 2 个要点即可得满分;完全正确需覆盖 prefetch 配置、队列拆分策略和大/小 prefetch 权衡三个维度。
|
|||
|
|
|
|||
|
|
## 关联笔记
|
|||
|
|
- [[04.MQ/rabbitmq/消息持久化与可靠性投递]]
|
|||
|
|
- [[04.MQ/rabbitmq/ACK 确认与死信队列]]
|
|||
|
|
- [[04.MQ/rabbitmq/Exchange 路由机制]]
|