Skip to content

RocketMQ vs Kafka 深度对比

面试官:你们项目为什么选用 RocketMQ 而不是 Kafka?

:我们是电商交易系统,核心需求是订单超时取消(延迟消息)、下单与扣库存的原子性(事务消息)、订单状态流转的顺序性保障(顺序消息),这些业务特性 Kafka 都不支持。Kafka 更适合日志收集、大数据管道这类超高吞吐场景。

面试官:能具体说说两者的架构差异吗?为什么 RocketMQ 更适合业务消息?

这个问题很多候选人只能说出”RocketMQ 有延迟消息”,但真正能从架构设计层面讲清楚两者差异的,才能体现出技术深度。


Q1:RocketMQ 和 Kafka 的核心架构有什么区别?必考

Section titled “Q1:RocketMQ 和 Kafka 的核心架构有什么区别?”

Kafka 架构(去中心化协调)

┌─────────────────────────────────────────────────────────────┐
│ ZooKeeper / KRaft │
│ (集群元数据、Leader 选举、Controller 协调) │
└─────────────────────────────────────────────────────────────┘
┌────────────────────┼────────────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Broker1 │ │ Broker2 │ │ Broker3 │
└─────────┘ └─────────┘ └─────────┘
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│Topic A │ │Topic A │ │Topic A │
│Part 0 │ │Part 1 │ │Part 2 │
│(独立文件)│ │(独立文件)│ │(独立文件)│
└─────────┘ └─────────┘ └─────────┘

RocketMQ 架构(轻量级协调)

┌─────────────────────────────────────────────────────────────┐
│ NameServer 集群 │
│ (无状态路由注册中心,不参与选举,每个节点独立) │
└─────────────────────────────────────────────────────────────┘
┌────────────────────┼────────────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Broker1 │ │ Broker2 │ │ Broker3 │
│─────────│ │─────────│ │─────────│
│CommitLog│ │CommitLog│ │CommitLog│
│(统一存储)│ │(统一存储)│ │(统一存储)│
│─────────│ │─────────│ │─────────│
│ConsumeQ │ │ConsumeQ │ │ConsumeQ │
│(索引文件)│ │(索引文件)│ │(索引文件)│
└─────────┘ └─────────┘ └─────────┘

核心架构差异

维度KafkaRocketMQ
协调中心ZooKeeper/KRaft(重量级,有选举开销)NameServer(轻量级,无状态,无选举)
存储模型Partition 独立存储(每个 Partition 一个文件目录)CommitLog 统一存储(所有 Topic 共用一个大文件)
元数据管理集中式存储在 ZK,Controller 协调分散在 NameServer,Broker 定时注册
高可用机制ISR 副本机制,Leader 选举依赖 ZKMaster-Slave 同步,无选举(4.5 后支持选主)
适用场景Topic 少、超高吞吐、日志流Topic 多、业务复杂、特性丰富

本质一句话:Kafka 用重量级协调组件换取强一致性和高吞吐,RocketMQ 用轻量级架构换取灵活性和业务特性支持。


Q2:CommitLog 统一存储 vs Partition 独立存储,各有什么优劣?高频

Section titled “Q2:CommitLog 统一存储 vs Partition 独立存储,各有什么优劣?”

写入性能对比

Kafka 写入流程:
Producer 批量发送 → 多 Partition 并发写入
├── Partition 0 → 文件 A(顺序写)
├── Partition 1 → 文件 B(顺序写)
└── Partition 2 → 文件 C(顺序写)
问题:当 Topic/Partition 数量 > 磁盘磁头数时,写入退化为随机 IO
实测:单机 100 个 Topic 时,Kafka 写入性能下降 40%
RocketMQ 写入流程:
Producer 发送 → 所有消息写入单一 CommitLog(严格顺序写)
└── 无论多少 Topic,始终顺序写同一个文件
优势:Topic 数量对写入性能几乎无影响(Page Cache 合并写)
实测:单机 1000 个 Topic,写入性能仍稳定

读取性能对比

Kafka 读取流程:
Consumer → 直接从 Partition 文件顺序读取(零拷贝)
RocketMQ 读取流程:
Consumer → ConsumeQueue 索引文件(20 字节/条)
→ 定位 CommitLog Offset
→ 从 CommitLog 读取消息体(需要一次跳转)
劣势:RocketMQ 读取多一次索引跳转,理论上性能略低
实测:单机 QPS < 10 万时差异可忽略,> 50 万时 RocketMQ 读性能下降 10-15%

性能数据对比(单机 16 核 32GB 内存 SSD):

场景Kafka 吞吐RocketMQ 吞吐结论
单 Topic,单 Partition/Queue100 万条/秒70 万条/秒Kafka 更快(零拷贝优势)
10 个 Topic,每个 10 Partition/Queue80 万条/秒70 万条/秒性能接近
100 个 Topic40 万条/秒65 万条/秒RocketMQ 反超(Kafka 随机 IO)
消息体 10KB(大消息)600 MB/秒500 MB/秒Kafka 更快(批量优势)
消息体 100 字节(小消息)800 MB/秒750 MB/秒性能接近

Q3:为什么 Kafka 用 ZooKeeper 而 RocketMQ 用 NameServer?中频

Section titled “Q3:为什么 Kafka 用 ZooKeeper 而 RocketMQ 用 NameServer?”

ZooKeeper 的职责(Kafka 2.x)

1. Broker 注册与存活监控
/brokers/ids/[0,1,2] → 临时节点,Broker 下线自动删除
2. Topic 元数据管理
/brokers/topics/[topic-name]/partitions/[0]/state
→ 记录 Partition 的 Leader、ISR、AR
3. Controller 选举
/controller → 第一个创建该节点的 Broker 成为 Controller
→ Controller 负责分区副本迁移、Leader 选举
4. Consumer Group 偏移量管理(旧版本)
/consumers/[group-id]/offsets/[topic]/[partition]

NameServer 的职责(RocketMQ)

1. Broker 注册与路由信息
Broker 启动时向所有 NameServer 注册(定时心跳 30s)
→ 存储 topic → Broker 地址的映射关系
2. Topic 路由查询
Producer/Consumer 启动时从 NameServer 获取 Topic 路由
→ 缓存在本地,定期更新
3. 无状态设计
NameServer 节点之间无通信,每个节点存储完整的路由信息
→ Broker 向所有 NameServer 注册,一个 NameServer 挂了不影响
不负责:
❌ Broker 故障转移(Broker 自己处理)
❌ Leader 选举(Broker 主从复制)
❌ Consumer Group 管理(Broker 维护)

设计取舍

维度ZooKeeperNameServer
复杂度高(需要维护 ZK 集群,学习 ZAB 协议)低(几千行代码,纯内存存储)
一致性强一致(ZAB 协议)最终一致(Broker 定时注册)
运维成本高(独立集群,监控、扩容)低(与 Broker 同机部署即可)
故障影响ZK 不可用时无法创建 Topic、选主单个 NameServer 挂了不影响,多个挂了才影响
适用场景需要强一致协调(如选主)只需路由注册(简单高效)

RocketMQ 不用 ZooKeeper 的原因

  1. 业务消息场景不需要强一致选主:Broker 主从复制是同步/异步复制,Master 故障时人工切换或自动切换(4.5 后),不需要 ZK 参与
  2. 降低运维复杂度:电商系统已经有了 DB、Redis、MQ,再加一个 ZK 集群是负担
  3. 性能考虑:ZK 的写请求需要过半节点确认,NameServer 的注册请求无需确认,性能更高

Q4:RocketMQ 的 Push 消费是真的 Push 吗?高频

Section titled “Q4:RocketMQ 的 Push 消费是真的 Push 吗?”

Push vs Pull vs Long Polling 对比

模式原理优点缺点
Push(真推送)Broker 主动推送消息给 Consumer实时性极高Consumer 可能处理不过来,消息积压在内存
Pull(拉取)Consumer 定时轮询 Broker 拉取消息Consumer 控制节奏,防止过载轮询间隔影响实时性,空轮询浪费资源
Long Polling(长轮询)Consumer 发起 Pull,Broker 有消息立即返回,无消息则挂起等待兼顾实时性和可控性实现复杂,需要维护连接状态

RocketMQ 的 “Push” 实现原理

// DefaultMQPushConsumer 源码(名字叫 Push,实为长轮询)
public class PullMessageService extends ServiceThread {
@Override
public void run() {
while (!this.isStopped()) {
PullRequest pullRequest = this.pullRequestQueue.take();
this.pullMessage(pullRequest); // 不断发起 Pull 请求
}
}
private void pullMessage(final PullRequest pullRequest) {
// 构建 Pull 请求,设置挂起时间(默认 15s)
PullResult pullResult = broker.pull(
pullRequest.getMessageQueue(),
pullRequest.getOffset(),
32, // 批量拉取 32 条
15000 // 挂起时间 15s
);
if (pullResult.getPullStatus() == FOUND) {
// 找到消息,立即处理,并发起下一个 Pull
processPullResult(pullResult);
pullRequestQueue.offer(pullRequest); // 立即发起下一次 Pull
} else if (pullResult.getPullStatus() == NO_NEW_MSG) {
// 没有新消息,Broker 挂起 15s 后返回
// 等 Broker 有新消息时主动唤醒返回
pullRequestQueue.offer(pullRequest); // 再次发起 Pull
}
}
}

Broker 端的长轮询实现

Consumer 发起 Pull 请求 → Broker 接收请求
├── 有消息 → 立即返回消息
└── 无消息 → 将请求挂起到 PullRequestHoldService
└── 等待新消息到达或超时(15s)
├── 有新消息到达 → 唤醒挂起的请求,返回消息
└── 15s 超时 → 返回 NO_NEW_MSG

Kafka 的 Pull 模型

// Kafka Consumer 也是 Pull,但不是长轮询
while (true) {
ConsumerRecords<String, String> records = consumer.poll(
Duration.ofMillis(1000) // 最多等待 1s
);
for (ConsumerRecord<String, String> record : records) {
process(record);
}
// 无论有没有消息,都会在 1s 后返回
}

对比总结

维度RocketMQ PushKafka Pull
实时性极高(新消息到达立即唤醒返回)高(最多延迟 poll timeout)
空轮询开销低(无消息时挂起,不占用 CPU)高(每秒轮询一次,无消息也消耗 CPU)
消费者可控是(通过调整 Pull 批量大小和间隔)是(通过调整 poll timeout)
实现复杂度高(需要 Broker 维护挂起请求)低(简单的轮询)

本质一句话:RocketMQ 的 Push 本质是 Long Polling,通过”Broker 挂起请求”换取实时性和低开销,是 Push 和 Pull 的最佳平衡。


Q5:RocketMQ 和 Kafka 的消费者组模型有什么差异?中频

Section titled “Q5:RocketMQ 和 Kafka 的消费者组模型有什么差异?”

消费模型对比

Kafka 消费模型:
Consumer Group A (3 个 Consumer)
├── Consumer 1 → 消费 Partition 0
├── Consumer 2 → 消费 Partition 1
└── Consumer 3 → 消费 Partition 2
规则:一个 Partition 只能被同一 Consumer Group 的一个 Consumer 消费
问题:Consumer 数量 > Partition 数量时,多余的 Consumer 空闲
RocketMQ 消费模型:
Consumer Group A (3 个 Consumer)
├── Consumer 1 → 消费 Queue 0, Queue 1
├── Consumer 2 → 消费 Queue 2, Queue 3
└── Consumer 3 → 消费 Queue 4, Queue 5
规则:一个 MessageQueue 只能被同一 Consumer Group 的一个 Consumer 消费
优势:Queue 数量可动态调整,更容易负载均衡

广播消费对比

维度KafkaRocketMQ
广播消费❌ 不支持(需要创建多个 Consumer Group)✅ 原生支持 MessageModel.BROADCASTING
实现方式每个 Consumer 使用不同的 Group ID同一 Group 的所有 Consumer 都收到全量消息
适用场景需要多个系统独立消费同一消息配置更新通知、缓存刷新

RocketMQ 广播消费代码示例

DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(
"broadcast_consumer_group"
);
consumer.setMessageModel(MessageModel.BROADCASTING); // 设置广播模式
consumer.subscribe("CONFIG_UPDATE_TOPIC", "*");
consumer.registerMessageListener((msgs, context) -> {
for (MessageExt msg : msgs) {
// 所有 Consumer 实例都会收到全量消息
refreshLocalCache(new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});

消费进度管理对比

维度KafkaRocketMQ
存储位置__consumer_offsets Topic(Broker 端)Broker 端(consumerOffset.json
提交方式enable.auto.commit=true 自动提交,或手动 commitSync()自动提交(默认 5s 一次),或手动提交
重置策略auto.offset.reset=earliest/latestsetConsumeFromWhere(FromFirst/FromLast)
精确度可精确到单条消息(手动提交)可精确到单条消息

Q6:RocketMQ 有哪些 Kafka 不支持的业务特性?必考

Section titled “Q6:RocketMQ 有哪些 Kafka 不支持的业务特性?”

特性支持对比表

特性RocketMQKafka业务价值
延迟消息✅ 18 级固定延迟(4.x)
✅ 任意精度延迟(5.0)
❌ 不支持订单超时取消、定时任务触发
事务消息✅ Half 消息 + 两阶段提交❌ 仅支持事务性写入(语义不同)下单与扣库存原子性
顺序消息✅ 分区顺序消息✅ Partition 内有序订单状态流转、用户操作序列
消息过滤✅ Tag 过滤
✅ SQL92 过滤(服务端)
❌ 仅客户端过滤减少网络传输,提高效率
死信队列✅ 自动转入 DLQ❌ 需手动实现异常消息隔离与人工干预
消息回溯✅ 按时间点回溯✅ 按 Offset 回溯数据恢复、问题排查
消息轨迹✅ 原生支持(发送、存储、消费全链路)❌ 需自行实现故障排查、性能分析
消费重试✅ 16 级延迟重试(自动)❌ 需手动实现临时故障自动恢复

延迟消息实战案例

业务场景:订单超时取消(用户下单 30 分钟未支付,自动取消)
RocketMQ 实现(简单高效):
1. 下单时发送延迟消息(delayLevel=16,延迟 30 分钟)
2. 消费者收到消息后检查订单状态
3. 未支付 → 取消订单,释放库存
4. 已支付 → 忽略(幂等处理)
Kafka 实现(复杂且不可靠):
方案 1:定时任务扫库
- 每分钟扫描一次 `orders` 表,查找超时未支付订单
- 问题:订单量大时扫库压力大,实时性差(最多延迟 1 分钟)
方案 2:Redis ZSet + 定时任务
- 将订单 ID 和超时时间戳存入 ZSet
- 定时任务每秒执行 `ZRANGEBYSCORE key 0 now` 获取到期订单
- 问题:需要维护额外存储,Redis 故障会丢数据
性能对比:
- RocketMQ 延迟消息:单机支持 10 万级订单超时,延迟精确到秒
- 定时任务扫库:单机处理 1 万订单/分钟,延迟 1-5 分钟

消息过滤实战案例

// 场景:订单消息,不同系统订阅不同类型的订单
// RocketMQ 服务端过滤(减少网络传输)
producer.send(new Message("ORDER_TOPIC", "TAG_NEW_ORDER", body)); // 新订单
producer.send(new Message("ORDER_TOPIC", "TAG_CANCEL_ORDER", body)); // 取消订单
// 消费者 1:只订阅新订单
consumer.subscribe("ORDER_TOPIC", "TAG_NEW_ORDER || TAG_NEW_ORDER"); // 服务端过滤
// 消费者 2:订阅所有订单
consumer.subscribe("ORDER_TOPIC", "*"); // 接收所有 Tag
// Kafka 客户端过滤(所有消息都会传输到客户端)
consumer.subscribe("ORDER_TOPIC");
while (true) {
for (ConsumerRecord<String, String> record : consumer.poll()) {
if (record.value().contains("\"type\":\"NEW_ORDER\"")) { // 客户端过滤
processNewOrder(record.value());
}
}
}

性能对比

场景RocketMQ 服务端过滤Kafka 客户端过滤
Topic 总消息量10 万条/秒10 万条/秒
消费者只需要 10%网络传输 1 万条/秒网络传输 10 万条/秒,客户端丢弃 9 万条
网络带宽10 MB/秒100 MB/秒(浪费 90%)
Consumer CPU低(只处理需要的消息)高(需要过滤 90% 消息)

Q7:在什么场景下选 Kafka?什么场景下选 RocketMQ?实战

Section titled “Q7:在什么场景下选 Kafka?什么场景下选 RocketMQ?”

选型决策树

开始选型
├─ 是否需要延迟消息/事务消息/死信队列?
│ └─ 是 → 选 RocketMQ
│ └─ 否 → 继续
├─ Topic 数量是否 > 50?
│ └─ 是 → 选 RocketMQ(CommitLog 优势)
│ └─ 否 → 继续
├─ 单机 QPS 是否 > 50 万?
│ └─ 是 → 选 Kafka(零拷贝优势)
│ └─ 否 → 继续
├─ 是否需要与大数据生态集成(Spark/Flink)?
│ └─ 是 → 选 Kafka(生态成熟)
│ └─ 否 → 继续
├─ 团队是否有 Kafka 运维经验?
│ └─ 是 → 选 Kafka
│ └─ 否 → 选 RocketMQ(运维简单)

典型场景推荐

场景推荐方案理由
订单系统RocketMQ需要延迟消息(订单超时)、事务消息(下单扣库存)
支付系统RocketMQ需要事务消息、顺序消息、死信队列
日志收集Kafka超高吞吐、与 ELK/Spark 集成方便
大数据管道Kafka与 Flink/Spark Streaming 深度集成
配置中心通知RocketMQ广播消费、消息过滤
用户行为埋点Kafka写入量极大(百万级 QPS),不需要复杂特性
金融交易RocketMQ需要事务消息、顺序消息、消息轨迹

实战案例:某电商平台选型过程

业务需求:
1. 订单系统:日均 500 万订单,需要订单超时取消(延迟消息)
2. 库存系统:需要事务消息保证下单与扣库存原子性
3. 日志收集:日均 10 TB 日志数据
4. Topic 数量:预计 200+ 个业务 Topic
选型决策:
- 业务消息(订单/库存/支付)→ RocketMQ 集群(3 主 3 从)
- 理由:需要延迟消息、事务消息、Topic 数量多
- 日志收集 → Kafka 集群(5 节点)
- 理由:超高吞吐、与 Flink/Spark 集成
最终方案:RocketMQ + Kafka 双集群,各取所长