文章

消息队列中间件面试题 —— 从 Kafka 架构到消息可靠性的深度问答

覆盖Kafka架构(分区/副本/ISR/零拷贝/消费者组)、RabbitMQ(交换机/AMQP/死信队列)、RocketMQ事务消息、消息顺序性/幂等性/积压处理、MQ选型对比,25 道高频题附架构图

消息队列中间件面试题 —— 从 Kafka 架构到消息可靠性的深度问答

消息队列是后端系统的核心中间件——几乎所有分布式系统都依赖 MQ 做解耦、异步和削峰。面试中能讲清 Kafka 的零拷贝原理、消息丢失的全链路防护、顺序消息的实现方案,展示的是对分布式系统的实战理解

这篇文章从核心概念 → Kafka 深入 → RabbitMQ → RocketMQ → 通用难题五条线展开,每道题都带架构图和方案对比

📌 关联阅读:系统设计面试题 · Redis 与缓存架构面试题 · 分布式理论面试题


第一部分:消息队列核心概念

Q1:为什么要用消息队列?核心作用是什么?

记忆点解耦、异步、削峰三板斧

1
2
3
4
5
6
7
8
9
10
11
同步调用(无MQ):
  用户下单 → 扣库存 → 扣积分 → 发短信 → 返回成功
  总耗时 = 50+50+50+50 = 200ms
  任一服务挂 → 整个下单失败

异步解耦(有MQ):
  用户下单 → 发消息到MQ → 返回成功(50ms)
                ↓
      ┌─────────┼──────────┐
      ↓         ↓          ↓
    扣库存    扣积分     发短信    ← 异步消费,互不影响
作用说明示例
解耦上游不关心下游有多少消费者订单服务不依赖短信服务
异步非核心流程异步处理下单后异步发通知
削峰突发流量先进 MQ,消费者匀速处理秒杀请求进队列
广播一条消息多个消费者处理数据变更通知多个系统
最终一致性配合重试实现跨服务事务分布式事务

引入 MQ 的代价:系统复杂度增加、消息可靠性保障、消息顺序问题、消息积压风险。


Q2:Kafka、RabbitMQ、RocketMQ 怎么选?

记忆点Kafka 大数据流,RabbitMQ 企业可靠,RocketMQ 电商事务

维度KafkaRabbitMQRocketMQ
定位分布式流处理平台企业级消息中间件分布式消息/事务
吞吐量百万级/秒万级/秒十万级/秒
延迟ms 级μs 级(最低)ms 级
消息模型拉模型(Pull)推模型(Push)拉模型(Pull)
消息回溯✅(按 offset)✅(按时间戳)
事务消息有(不常用)✅(半消息+回查)
延迟消息❌(需插件)✅(TTL+DLX)✅(18个等级)
消息查询按 offset✅(按 msgId/key)
协议自定义AMQP自定义
语言Scala/JavaErlangJava
典型用户LinkedIn/Uber银行/电信阿里/滴滴

选型建议

场景推荐原因
日志采集/大数据Kafka吞吐量最高
实时流处理KafkaKafka Streams/Flink 生态
企业级可靠消息RabbitMQ协议完善、路由灵活
低延迟业务消息RabbitMQμs 级延迟
电商交易/事务RocketMQ事务消息原生支持
延迟消息/定时任务RocketMQ延迟消息原生支持

第二部分:Kafka 深入

Q3:Kafka 的整体架构是什么?

记忆点Topic → Partition → Replica → Segment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Kafka 集群架构:

Producer ──→ ┌──────────────────────────────┐
             │         Kafka Cluster         │
             │ ┌──────────┐ ┌──────────┐     │
             │ │ Broker 0 │ │ Broker 1 │     │
             │ │ P0(L)    │ │ P0(F)    │     │
             │ │ P1(F)    │ │ P1(L)    │     │
             │ └──────────┘ └──────────┘     │
             │ ┌──────────┐                  │
             │ │ Broker 2 │  L=Leader        │
             │ │ P0(F)    │  F=Follower      │
             │ │ P1(F)    │                  │
             │ └──────────┘                  │
             └──────────────────────────────┘
                              ↓
Consumer Group ──→ Consumer1(P0), Consumer2(P1)

Topic "orders" 有 2 个 Partition,3 个副本(replication-factor=3)

核心概念速查

概念说明
BrokerKafka 服务器节点
Topic逻辑消息分类
PartitionTopic 的分片,是并行度的基本单位
ReplicaPartition 的副本(1 Leader + N Follower)
ISRIn-Sync Replicas,与 Leader 保持同步的副本集合
Offset消息在 Partition 中的唯一偏移量
Consumer Group消费者组,组内每个 Partition 只被一个消费者消费

Q4:Kafka 为什么吞吐量这么高?

记忆点顺序写 + 页缓存 + 零拷贝 + 批量 + 分区并行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
五大性能优化:

1. 顺序写磁盘(追加写入,不随机写)
   随机写 HDD: ~100 IOPS
   顺序写 HDD: ~600 MB/s  ← 快 6000 倍!

2. 页缓存(Page Cache)
   写入不直接落盘,先写 OS 页缓存
   → 写入速度 ≈ 内存速度
   → 消费者读取热数据也命中页缓存

3. 零拷贝(sendfile)
   传统:磁盘 → 内核缓冲 → 用户缓冲 → Socket缓冲 → 网卡
   零拷贝:磁盘 → 内核缓冲 ──────────────────→ 网卡
   → 消费者拉取时使用 sendfile,减少 2 次数据拷贝

4. 批量发送 + 压缩
   Producer 攒一批消息一起发(linger.ms + batch.size)
   支持 gzip/snappy/lz4/zstd 压缩

5. 分区并行
   多个 Partition → 多个消费者并行消费
   → 水平扩展能力

Q5:Kafka 的 ISR 机制是什么?消息什么时候算”已提交”?

记忆点:ISR = 和 Leader 保持同步的副本集合,只有 ISR 中的副本参与提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ISR 动态变化:

初始 ISR = {Leader, Follower1, Follower2}

Follower2 落后太多(replica.lag.time.max.ms 超时):
  ISR = {Leader, Follower1}  ← Follower2 被踢出

Follower2 追上 Leader:
  ISR = {Leader, Follower1, Follower2}  ← 重新加入

消息提交条件(acks 配置):
  acks=0    Producer 不等确认         → 可能丢消息
  acks=1    Leader 写入就确认          → Leader 挂了可能丢
  acks=all  所有 ISR 副本写入才确认    → 最安全 ✅

  min.insync.replicas=2  配合 acks=all
  → 至少 2 个 ISR 副本确认才算提交
  → 如果 ISR 只剩 1 个,拒绝写入(保证不丢)

Q6:Kafka 的消费者组(Consumer Group)是怎么工作的?

记忆点组内分区独占,组间广播

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Topic: orders (3 Partitions)

Consumer Group A(3个消费者):
  Consumer1 ← P0    每个消费者消费不同分区
  Consumer2 ← P1    → 组内负载均衡
  Consumer3 ← P2

Consumer Group B(2个消费者):
  Consumer4 ← P0, P1   消费者少于分区数
  Consumer5 ← P2       → 有的消费者消费多个分区

Consumer Group C(4个消费者):
  Consumer6 ← P0
  Consumer7 ← P1       消费者多于分区数
  Consumer8 ← P2       → Consumer9 闲置!
  Consumer9 ← (空闲)

关键规则:
- 同一 Group 内,一个 Partition 只能被一个 Consumer 消费
- 不同 Group 之间,消息会被广播(每个 Group 都能消费到)
- 消费者数量 > 分区数 → 多余消费者闲置

Rebalance(再均衡)触发条件

  • 消费者加入/退出 Group
  • 订阅的 Topic 分区数变化
  • 消费者被判定超时(session.timeout.ms)

Q7:Kafka 的消息存储结构是什么?

记忆点Partition = 多个 Segment 文件,每个 Segment 有 .log + .index + .timeindex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Partition 目录结构:
topic-orders-0/
├── 00000000000000000000.log        ← 消息数据(append-only)
├── 00000000000000000000.index      ← 稀疏偏移量索引
├── 00000000000000000000.timeindex  ← 时间戳索引
├── 00000000000005242880.log        ← 新 Segment(基于大小/时间滚动)
├── 00000000000005242880.index
└── 00000000000005242880.timeindex

查找 offset=5242900 的消息:
1. 二分查找 Segment 文件(文件名就是起始 offset)
   → 定位到 00000000000005242880.log
2. 在 .index 中二分查找最近的索引条目
   → 找到 offset 5242888 → 物理位置 12345
3. 从物理位置 12345 开始顺序扫描到 offset 5242900

第三部分:RabbitMQ

Q8:RabbitMQ 的消息模型和 Exchange 类型?

记忆点Producer → Exchange → Queue → Consumer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
RabbitMQ 消息路由模型:

Producer → Exchange ─(routing_key)─→ Queue → Consumer

四种 Exchange 类型:

1. Direct:精确匹配 routing_key
   Exchange ─ routing_key="order.created" ─→ Queue A
             ─ routing_key="order.paid"    ─→ Queue B

2. Topic:通配符匹配
   Exchange ─ "order.*"    ─→ Queue A(匹配 order.xxx)
             ─ "order.#"   ─→ Queue B(匹配 order.xxx.yyy...)
   * = 匹配一个词, # = 匹配零或多个词

3. Fanout:广播(忽略 routing_key)
   Exchange ─→ Queue A
             ─→ Queue B    所有绑定的队列都收到
             ─→ Queue C

4. Headers:根据消息头匹配(不常用)

Q9:RabbitMQ 怎么保证消息不丢失?

记忆点三个环节都要保障:生产端 → Broker → 消费端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
消息丢失的三个环节及解决方案:

1. 生产端 → Broker(网络丢失)
   解决:Publisher Confirm 机制
   channel.confirmSelect();
   channel.waitForConfirms();  // 同步等待 Broker 确认
   // 或异步回调 addConfirmListener

2. Broker 持久化(宕机丢失)
   解决:队列持久化 + 消息持久化
   Queue: durable=true
   Message: deliveryMode=2(持久化)
   → 消息写入磁盘后才确认

3. 消费端(处理前 ack 导致丢失)
   解决:手动 ACK
   autoAck=false
   channel.basicAck(tag, false);  // 处理完成后手动确认
   → 处理失败时 basicNack 重回队列

Q10:RabbitMQ 的死信队列(DLX)是什么?有什么用?

记忆点:死信 = 被拒绝/过期/队列满的消息,转发到死信交换机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
死信产生条件:
1. 消息被 basicNack/basicReject 且 requeue=false
2. 消息 TTL 过期
3. 队列达到最大长度

死信队列流程:
  Normal Exchange → Normal Queue ──(死信)──→ DLX Exchange → DLX Queue
                         ↑                                      ↑
                    消息过期/被拒                          人工处理/重试

实际应用:
1. 延迟队列(TTL + DLX)
   消息设 TTL=30min → 过期后进入 DLX → 消费者处理
   → 30 分钟后执行(如:订单超时取消)

2. 异常消息兜底
   消费失败的消息进 DLX → 告警/人工排查

第四部分:RocketMQ

Q11:RocketMQ 的事务消息是怎么实现的?

记忆点半消息 + 本地事务 + 回查机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
RocketMQ 事务消息流程:

Producer                  Broker                    Consumer
   │                        │                          │
   │ 1.发送半消息(HALF)      │                          │
   │ ─────────────────→     │  存储半消息               │
   │ ←──── 发送成功 ─────── │  (对消费者不可见)         │
   │                        │                          │
   │ 2.执行本地事务          │                          │
   │ (扣款/下单等)           │                          │
   │                        │                          │
   │ 3.提交/回滚             │                          │
   │ COMMIT ──────────→     │ → 消息对消费者可见        │
   │ 或 ROLLBACK ─────→     │ → 删除半消息              │
   │                        │                          │
   │                        │                          │
   │ 如果 Producer 没响应    │                          │
   │ ←── 4.回查本地事务 ─── │  (定时回查)              │
   │ 返回 COMMIT/ROLLBACK → │                          │

关键:即使 Producer 宕机,Broker 也能通过回查确认事务状态

对比两阶段提交

维度2PCRocketMQ 事务消息
协调者事务管理器MQ Broker
阻塞同步阻塞异步非阻塞
可用性协调者单点Broker 高可用
适用强一致性最终一致性

Q12:RocketMQ 的延迟消息怎么实现的?

记忆点18 个延迟等级,用定时任务投递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
延迟等级(不支持任意时间):
1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

实现原理:
1. Producer 发送延迟消息,指定 delayLevel=3(10s)
2. Broker 将消息存入特殊的 SCHEDULE_TOPIC(按等级分队列)
3. 定时任务每隔 1s 检查到期的消息
4. 到期 → 将消息投递到原始 Topic → 消费者可见

时间线:
T=0    Producer 发送 delayLevel=3(10s延迟)
T=0    Broker 存入 SCHEDULE_TOPIC_XXXX Queue3
T=10s  定时任务发现到期 → 投递到原始 Topic
T=10s  Consumer 收到消息

第五部分:通用难题

Q13:如何保证消息不丢失?(全链路分析)

记忆点生产端 + Broker + 消费端三环防护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
全链路消息不丢失方案:

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  Producer    │    │   Broker    │    │  Consumer   │
│             │    │             │    │             │
│ 确认机制:    │→   │ 持久化:      │→   │ 手动ACK:     │
│ Kafka:acks=all   │ Kafka:       │    │ 处理完再确认  │
│ RabbitMQ:confirm │ replication  │    │ 失败重试     │
│ 重试+幂等    │    │ RabbitMQ:    │    │ 死信队列兜底  │
│             │    │ durable+持久 │    │             │
└─────────────┘    └─────────────┘    └─────────────┘

Kafka 具体配置:
  Producer:
    acks=all
    retries=MAX_INT
    enable.idempotence=true

  Broker:
    replication.factor >= 3
    min.insync.replicas >= 2
    unclean.leader.election.enable=false

  Consumer:
    enable.auto.commit=false(手动提交 offset)

Q14:如何保证消息不重复消费?(幂等性)

记忆点MQ 不保证 exactly-once,靠消费端幂等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
消息重复的原因:
1. Producer 发送超时重试 → Broker 收到 2 条
2. Consumer 处理成功但 ack 失败 → rebalance 后重新消费
3. 网络波动导致重复投递

消费端幂等方案:

方案1:唯一 ID + 去重表
  ┌────────────────────────────────────────┐
  │ 消费消息前:                             │
  │   SELECT * FROM dedup WHERE msg_id=?   │
  │   如果存在 → 跳过                       │
  │   如果不存在 → 处理 + INSERT msg_id     │
  │   (在同一个事务中)                     │
  └────────────────────────────────────────┘

方案2:数据库唯一约束
  INSERT INTO orders (order_id, ...) VALUES (?, ...)
  ON DUPLICATE KEY UPDATE ... (或忽略)
  → 天然幂等

方案3:Redis SET NX
  if redis.setnx("consumed:" + msgId, 1, ex=86400):
      process(msg)
  else:
      skip  // 已消费过

方案4:乐观锁/版本号
  UPDATE stock SET quantity=quantity-1
  WHERE product_id=? AND version=?
  → 重复执行不会多扣

Q15:如何保证消息的顺序性?

记忆点全局有序很难,通常只需分区有序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Kafka 顺序保证:
  同一 Partition 内消息有序
  → 需要顺序的消息发到同一 Partition

  // Producer: 按 key hash 到同一 partition
  producer.send(new ProducerRecord("orders", orderId, message));
  // 同一 orderId 的消息都进同一 partition → 有序

  // Consumer: 单线程消费该 partition
  // 如果多线程 → 用内存队列按 key 分发到固定线程

全局有序方案(性能很差,慎用):
  Topic 只用 1 个 Partition + 1 个 Consumer
  → 完全有序,但吞吐量极低

RocketMQ 顺序消息:
  // 发送时指定队列选择器
  producer.send(msg, (queues, msg, arg) -> {
      int id = (int) arg;
      return queues.get(id % queues.size());
  }, orderId);

实际场景:
  订单创建 → 支付 → 发货(同一订单 ID 需要有序)
  → 按 orderId hash 到同一 Partition
  → 分区内单线程消费

Q16:消息积压了怎么办?

记忆点先止血(扩容),再排查(找原因),最后优化(防复发)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
消息积压处理步骤:

1. 紧急止血(扩容消费者)
   ┌──────┐    ┌──────────────────────────┐
   │ MQ   │ →  │ 临时扩容消费者实例        │
   │积压100万│   │ Consumer × 10             │
   └──────┘    │ (需要分区数 ≥ 消费者数)  │
               └──────────────────────────┘
   如果分区数不够 → 临时创建新 Topic(更多分区)
   用一个转发程序把积压消息搬到新 Topic

2. 排查根因
   消费者挂了?→ 重启
   消费太慢?  → 优化消费逻辑(批量处理/异步)
   上游暴增?  → 限流/降级

3. 长期优化
   消费者性能优化(批量消费、异步IO)
   合理设置分区数(分区数 = 消费者数 × 2)
   监控告警(消费延迟 > 阈值就告警)
   消息 TTL(过期自动丢弃,适用于非关键消息)

Q17:如何实现延迟消息?各 MQ 的方案对比?

记忆点:各 MQ 支持程度不同,通用方案用时间轮

MQ延迟消息支持实现方式
Kafka❌ 原生不支持外部定时任务轮询
RabbitMQ✅ TTL + DLX消息过期后进死信队列
RocketMQ✅ 18个等级内部定时任务投递
Pulsar✅ 任意延迟原生支持

通用方案:Redis + 时间轮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
方案:Redis ZSet 做延迟队列
  ZADD delay_queue <execute_timestamp> <message>

  消费者循环:
  messages = ZRANGEBYSCORE delay_queue 0 <now> LIMIT 0 100
  for msg in messages:
      if ZREM delay_queue msg:  // 原子取出防重复
          publish(msg)          // 投递到正常队列

时间轮(Timing Wheel):
  ┌─────────────────────────────────────┐
  │  [0] [1] [2] [3] [4] [5] [6] [7]  │  ← 8个槽
  │   ↑                                │
  │   当前指针,每 tick 前进一格          │
  │   到达某槽 → 执行该槽的所有任务       │
  └─────────────────────────────────────┘
  多层时间轮可支持更长延迟(天/月级别)

第六部分:高可用与运维

Q18:Kafka 的 Leader 选举是怎么工作的?

记忆点Controller 统一管理,ISR 中优先选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Kafka Leader 选举机制:

1. Controller 选举(集群级别)
   所有 Broker 竞争在 ZooKeeper 创建 /controller 节点
   → 第一个创建成功的成为 Controller
   → Controller 负责管理所有 Partition 的 Leader 选举

2. Partition Leader 选举
   当某 Partition 的 Leader 挂了:
   Controller 检测到(通过 ZooKeeper watch)
   → 从 ISR 列表中选第一个存活的副本作为新 Leader
   → 通知所有 Broker 更新元数据

   ISR = [Broker1, Broker2, Broker3]
   Broker1(Leader) 挂了 → Broker2 成为新 Leader

   如果 ISR 为空?
   unclean.leader.election.enable=true  → 从非 ISR 中选(可能丢数据)
   unclean.leader.election.enable=false → 该 Partition 不可用(推荐)

Q19:Kafka 如何实现 Exactly-Once 语义?

记忆点幂等 Producer + 事务 = Exactly-Once

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
三种语义:
  At-most-once:  acks=0, 可能丢消息
  At-least-once: acks=all + 重试, 可能重复
  Exactly-once:  幂等 + 事务

1. 幂等 Producer(单分区去重)
   enable.idempotence=true
   → 每条消息带 <PID, Sequence> 编号
   → Broker 检测到重复序号 → 丢弃
   → 只保证单分区内去重

2. 事务(跨分区原子写入)
   producer.initTransactions();
   producer.beginTransaction();
   producer.send(record1);   // 写入 Partition A
   producer.send(record2);   // 写入 Partition B
   producer.commitTransaction();  // 原子提交
   // 要么全部可见,要么全部不可见

   Consumer 配置:
   isolation.level=read_committed
   → 只读取已提交的事务消息

Q20:MQ 集群监控应该关注哪些指标?

记忆点积压量 / 消费延迟 / 吞吐量 / 可用性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
核心监控指标:

┌─────────────────────────────────────────────┐
│ 指标              │ 告警阈值          │ 含义  │
├─────────────────────────────────────────────┤
│ Consumer Lag      │ > 10000           │ 消费积压│
│ 消费延迟(时间)    │ > 5min            │ 处理慢 │
│ 消息入队速率      │ 突增 3 倍         │ 流量暴增│
│ 消息出队速率      │ 骤降 50%          │ 消费异常│
│ Broker 磁盘使用率 │ > 80%             │ 存储告急│
│ ISR 收缩          │ ISR < replicas    │ 副本落后│
│ Under-replicated  │ > 0               │ 副本不足│
│ Request 延迟      │ p99 > 100ms       │ 性能下降│
└─────────────────────────────────────────────┘

Kafka 常用监控命令:
  # 查看消费者组 lag
  kafka-consumer-groups.sh --describe --group mygroup

  # 查看 Topic 分区信息
  kafka-topics.sh --describe --topic orders

  # 查看集群健康
  kafka-metadata.sh --snapshot /path/to/metadata

第七部分:实战场景

Q21:秒杀系统中消息队列怎么用?

记忆点前端限流 → MQ 削峰 → 后端匀速消费

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
秒杀架构:

用户请求(10万QPS)
    ↓ Nginx 限流
请求校验(库存预检/Redis)
    ↓ 通过的请求
写入 MQ(Kafka/RocketMQ)
    ↓ 消费者按固定速率处理
扣减库存(数据库)
    ↓
返回结果(异步通知/轮询)

关键设计:
1. 前端防刷:验证码 + 按钮灰置
2. 接入层限流:Nginx limit_req
3. Redis 预检:库存为 0 直接拒绝
4. MQ 削峰:高峰流量进队列
5. 下单服务:固定并发消费(如 100 QPS)
6. 数据库:乐观锁/分布式锁 防超卖

Q22:如何用消息队列实现分布式事务?

记忆点本地消息表 / 事务消息两种主流方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
方案1:本地消息表

Service A (订单服务)              MQ              Service B (库存服务)
     │                            │                    │
     │ BEGIN TRANSACTION           │                    │
     │ INSERT order                │                    │
     │ INSERT message_table        │                    │
     │ COMMIT                      │                    │
     │                            │                    │
     │ 定时任务扫描 message_table   │                    │
     │ ──── 发送消息 ────────→     │                    │
     │                            │ ──── 消费 ────→    │
     │                            │                    │ 扣减库存
     │                            │ ←── ACK ─────     │
     │ 标记消息已发送               │                    │

方案2:RocketMQ 事务消息(见 Q11)

对比:
  本地消息表:通用,任何 MQ 都行,但多了一张表
  事务消息:RocketMQ 原生支持,更优雅
  两者都是最终一致性

Q23:日志采集系统用 Kafka 怎么设计?

记忆点Filebeat → Kafka → Logstash/Flink → ES/HDFS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
日志采集架构(ELK + Kafka):

┌─────────┐  ┌─────────┐  ┌─────────┐
│ Server1 │  │ Server2 │  │ Server3 │
│Filebeat │  │Filebeat │  │Filebeat │  ← 日志采集
└────┬────┘  └────┬────┘  └────┬────┘
     │            │            │
     ↓            ↓            ↓
┌──────────────────────────────────┐
│           Kafka Cluster           │  ← 缓冲层
│  Topic: app-logs (30 partitions) │
└───────────┬──────────┬───────────┘
            ↓          ↓
     ┌──────────┐ ┌──────────┐
     │ Logstash │ │  Flink   │     ← 消费处理
     │ (清洗)   │ │ (实时分析)│
     └────┬─────┘ └────┬─────┘
          ↓            ↓
   ┌──────────┐  ┌──────────┐
   │  ES/Kibana│  │   HDFS   │     ← 存储
   │ (检索)    │  │ (归档)   │
   └──────────┘  └──────────┘

关键配置:
  retention.ms = 72h      (日志保留3天)
  cleanup.policy = delete (过期自动删除)
  compression.type = lz4  (压缩节省带宽)
  partitions = 服务器数 × 2

Q24:消息队列的消息轨迹追踪怎么做?

记忆点每条消息带 traceId,全链路记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
消息轨迹追踪:

Producer                    Broker                    Consumer
  │ 生成 traceId            │                          │
  │ 记录: 发送时间+Topic    │                          │
  │ ───── 发送 ─────→       │ 记录: 存储时间+Partition │
  │                         │ +offset                  │
  │                         │ ─── 投递 ──→             │
  │                         │             记录: 消费时间│
  │                         │             +处理结果     │

查询某消息的全链路:
  traceId=abc123
  → 生产端: 2026-02-27 10:00:00.123, Topic=orders
  → Broker: 2026-02-27 10:00:00.125, P2, offset=12345
  → 消费端: 2026-02-27 10:00:00.200, 处理成功

RocketMQ 原生支持消息轨迹(msgTraceEnable=true)
Kafka 需要自行实现(Header 中注入 traceId)

Q25:如何做 MQ 的灰度发布和消息迁移?

记忆点双写 + 双读 + 渐进切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
MQ 迁移步骤(如 RabbitMQ → Kafka):

Phase 1: 双写
  Producer → RabbitMQ(主)
  Producer → Kafka   (备)
  Consumer 只读 RabbitMQ

Phase 2: 双读验证
  Consumer 同时读两个 MQ
  对比消息一致性(日志/监控)

Phase 3: 切换
  Consumer 切为只读 Kafka
  Producer 切为只写 Kafka
  RabbitMQ 保留一段时间用于回滚

Phase 4: 下线
  确认无问题 → 下线 RabbitMQ

面试口诀速记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MQ 三板斧:解耦、异步、削峰
Kafka 大数据流,Rabbit 企业级,Rocket 做事务

Kafka 快在:顺序写 + 页缓存 + 零拷贝 + 批量 + 分区
ISR 同步副本集,acks=all 最安全
Consumer Group 组内分区独占,组间广播

不丢消息三环:生产确认 + 持久化 + 手动ACK
不重复靠幂等:唯一ID + 去重表 / 唯一约束
顺序靠分区:同 key 同 partition + 单线程消费

积压先扩容,再查原因,后做优化
延迟消息:Rabbit用TTL+DLX,Rocket用等级,通用用Redis ZSet
事务消息:Rocket半消息+回查,通用用本地消息表

这篇文章覆盖了消息队列中间件的核心面试考点。建议在本地搭一个 Kafka 单机版,亲手跑一遍 Producer/Consumer/Consumer Group,比看十遍文章都管用。

本文由作者按照 CC BY 4.0 进行授权