面试官:Kafka 为什么比 RocketMQ 快?——不是快,是场景不同
引言
面试场景还原:
面试官:"Kafka 为什么比 RocketMQ 快?"
候选人:"因为 Kafka 有零拷贝、顺序写、批量发送。"
面试官微微一笑:"那 RocketMQ 团队为什么不把这些抄过去?是技术不行吗?"
候选人:?
这一问就露馅了。因为题目的前提就是错的——Kafka 不是"更快的 MQ",RocketMQ 也不是"慢一点的 MQ"。它们是两个物种:
- Kafka 从 LinkedIn 的日志管道演化而来,本质是分布式日志系统,为海量数据的高吞吐流转而生
- RocketMQ 从阿里双 11 交易链路演化而来,本质是业务消息中间件,为交易场景的功能完备性而生
先亮核心结论:高吞吐的数据链路选 Kafka,业务交易链路选 RocketMQ。我们公司生产上两者并存——埋点日志、CDC、Flink 流处理走 Kafka(日处理 40 亿条);订单、支付、库存等业务消息走 RocketMQ,各干各的,谁也没想替代谁。
这篇文章按《本地缓存三巨头》的套路,从 5 个维度拆解两者差异:简介 → 架构对比 → 吞吐实测 → 功能完备性 → 延迟与消费体验,最后给出"谁也替代不了谁的 3 + 3 张底牌"和选型决策树。看完你不但能答好这道面试题,还能答好那道追问。
一、两位选手简介
1.1 Kafka
出身:2011 年 LinkedIn 开源,最初解决的是"网站行为日志从应用到 Hadoop 的搬运",后来 Apache 顶级项目,2021 年进入 Confluent 主导的时代。
定位:分布式提交日志(Commit Log)/ 流数据平台——把它当"MQ"其实是降维使用。
特点:
- 存储模型:每个分区一个独立日志文件,消费者拉取的是连续文件区间(这个设计后面反复出现)
- 四板斧:顺序写 + PageCache + 零拷贝 sendfile + 批量压缩(详见《Kafka 为什么这么快》)
- 分区并行:吞吐随分区数近乎线性扩展
- 生态:Flink/Spark/Debezium/Kafka Connect 的默认数据总线
现状:4.x 时代 KRaft 全面取代 ZooKeeper,运维复杂度显著下降;大数据领域事实标准。
# Kafka 的"快"大半写在客户端配置里——复杂性给了客户端
batch.size=65536 # 攒批 64KB
linger.ms=5 # 最多等 5ms
compression.type=zstd # 攒完压缩
enable.idempotence=true # 幂等,重试不乱序
1.2 RocketMQ
出身:2012 年阿里开源(前身 MetaQ),为双 11 交易链路打造,同年起承载双十一全部交易消息。
定位:业务消息中间件——消息队列里的"功能完全国产化重型选手"。
特点:
- 存储模型:所有队列的消息混存在单个 CommitLog,ConsumeQueue 只做索引(这个设计也后面反复出现)
- 四张功能牌:事务消息、延迟/定时消息、消息轨迹、重试与死信——全是服务端内建
- 架构轻组件:NameServer 无状态,不用 ZooKeeper
现状:Apache 顶级项目,5.x 引入 Proxy 模式(gRPC 多语言)与存储 KV 分离优化,国内金融/电商渗透率极高。
// RocketMQ 的"全"写在 API 里——复杂性收进了服务端
message.setDelayTimeSec(1800); // 延迟消息:30 分钟后投递,一行
producer.sendMessageInTransaction(msg, order); // 事务消息:半消息 + 本地事务 + 回查
// 消费失败 → 自动进重试队列(16 级退避)→ 耗尽自动进死信 %DLQ%
// 发送 → 存储 → 消费全程有轨迹可查
// —— 以上这些,在 Kafka 里全部要自己搭
1.3 一句话画像
| Kafka | RocketMQ | |
|---|---|---|
| 像什么 | 一本可订阅的分布式账本(追加写、随便翻旧账) | 一台功能齐全的挂号机(叫号、改约、投诉记录一应俱全) |
| 优化目标 | 每秒流转更多字节 | 每条业务消息更可靠、更可控 |
二、架构对比:两张图看懂"快"与"全"的分歧
2.1 两张架构图
【Kafka:轻 Broker,重客户端】
┌──────────────┐ ┌────────────────────────────────────┐ ┌────────────────┐
│ producer │ │ Broker 集群 │ │ consumer group │
│ 攒批/压缩 │─────▶│ P0: [log文件 + index] leader │─────▶│ 拉模式 poll │
│ 分区路由 │ │ follower(ISR 副本) │ │ 位点自己管理 │
│ 重试/幂等 │ │ P1: [log文件 + index] ... │ │ 故障转移自己做 │
└──────────────┘ └────────────────────────────────────┘ └────────────────┘
▲
元数据:KRaft(4.x 起,无 ZooKeeper)
客户端承担:攒批、压缩、分区路由、重试幂等、位点管理
服务端只做一件事:把日志文件顺序写好、按区间读出去
【RocketMQ:重 Broker,轻客户端】
┌─────────┐ 路由 ┌─────────────┐ ┌─────────────────────────────────┐
│producer │◀─────▶│ NameServer │ │ Broker Master │
│ │───────│ (无状态 ×2) │─────▶│ CommitLog:所有队列混存,顺序追加 │
└─────────┘ └─────────────┘ │ ConsumeQueue:按队列的索引 │
│ IndexFile:按 key 查消息 │
┌─────────┐ push(长轮询) │ Slave 同步复制 │
│consumer │◀──────────────────────────│ 事务回查 / 延迟队列 / 重试队列 │
└─────────┘ └─────────────────────────────────┘
▲
IndexFile + 轨迹 Topic:消息轨迹的数据基础
服务端承担:过滤、轨迹、事务回查、延迟调度、重试死信
客户端只做一件事:发消息、注册监听器
2.2 关键差异表
| 设计点 | Kafka | RocketMQ | 带来的后果 |
|---|---|---|---|
| 元数据管理 | KRaft(早期 ZK) | NameServer 无状态 ×2 | RocketMQ 组件更轻,但路由感知有秒级延迟 |
| 存储模型 | 每分区独立日志文件 | 单 CommitLog 混存 + ConsumeQueue 索引 | 决定了两者 IO 路径的分野(见 2.3) |
| 数据传输 | sendfile 零拷贝 | mmap(MappedByteBuffer) | Kafka 消费路径 CPU 更省 |
| 批量发送 | 客户端默认攒批 | 显式 batch API(≤4MB),默认逐条 | Kafka 高吞吐开箱即用 |
| 功能驻留 | 客户端 + 生态组件 | 服务端内建 | Kafka 功能升级要动客户端;RocketMQ 只动服务端 |
| 消费方式 | pull,参数自己调 | push(长轮询),开箱低延迟 | 见第五章 |
2.3 追问的正解:为什么 RocketMQ 不抄零拷贝?
面试官那句"为什么不抄过去",答案就藏在存储模型里。
sendfile 生效的前提是:文件连续区间 → 网卡,中间不碰数据内容。Kafka 的消费者按 offset 拉取,offset 直接映射为"分区日志文件里的连续字节区间",数据全程不用进用户态,所以 sendfile 一路绿灯。
再看 RocketMQ 的消费路径:
消费者要拉 queue-3 的 offset=1000 的消息:
ConsumeQueue 定位 → 得到 CommitLog 的物理偏移
↓
CommitLog 是【所有 Topic 所有队列混存】的同一个文件
——这一段里可能混着订单、库存、短信的消息
↓
必须解析消息体:按 Tag/SQL92 过滤、处理属性、埋轨迹、查事务状态
↓
数据必须进用户态 → sendfile 失效
也就是说:不是 RocketMQ 不想要零拷贝,是"服务端要看得见消息内容"这个功能定位,逼着数据走进用户态——事务回查、延迟调度、Tag 过滤、轨迹记录,每一个服务端功能都建立在"broker 能解析每条消息"之上。作为折中,RocketMQ 用 mmap 把 CommitLog 映射进内存(省掉内核态到用户态的一次拷贝),配合 transientStorePool 优化写路径。
这就是全文最重要的一句话:Kafka 的快和 RocketMQ 的全,是同一个架构分歧的两面——Kafka 把消息当"不透明字节流"存,才换来零拷贝和极致吞吐;RocketMQ 把消息当"一等公民对象"存,才换来服务端功能全家桶。 存储模型决定了 IO 路径,IO 路径决定了性能上限。
三、维度一:吞吐量——Kafka 的主场
3.1 测试环境
| 项目 | 配置 |
|---|---|
| 机器 | 3 台 16C 64G,NVMe SSD,万兆内网(同一批机器分别部署) |
| Kafka 3.8 | 3 Broker,RF=3,acks=all + 幂等,zstd(与《Kafka 为什么这么快》同一套基准) |
| RocketMQ 5.x | 3 Master + 3 Slave,同步复制;2KB 订单消息,与 Kafka 相同 |
| 压测工具 | 各自官方 benchmark 脚本调到同规格 |
3.2 测试结果
| 持久化语义 | Kafka | RocketMQ |
|---|---|---|
| 副本可靠 + 异步落盘 | 80 万 msg/s(acks=all,靠副本代替刷盘) | 32 万 msg/s(异步刷盘 + 同步复制) |
| 强持久化 | 无此概念(Kafka 不支持 broker 级同步刷盘) | 15 万 msg/s(同步刷盘 + 同步复制) |
| 磁盘 util | < 30%(热数据在 PageCache) | 60%+(每条消息都要构建索引 + 刷盘) |
先解释一个公平性问题:Kafka 用多副本的网络冗余代替 fsync 保数据安全(《Kafka 为什么这么快》8.2 讲过原理),RocketMQ 的同步刷盘是真·每次落盘,语义更重。所以对比"同级语义"看第二行:Kafka 80 万 vs RocketMQ 32 万,差距约 2.5 倍;对比"最强持久化"差距约 5 倍。
3.3 数据分析
差距拆到三个来源,每个都能在架构图里找到对应:
| 吞吐差距来源 | Kafka 的做法 | RocketMQ 的做法 | 损失量级 |
|---|---|---|---|
| 消费路径 | sendfile,数据不进用户态 | 必须解析消息体,mmap 进用户态 | 消费端 CPU 开销高数倍 |
| 攒批 | 客户端默认攒批 + 压缩 | 默认逐条同步发,batch 要显式开 | 单条 RPC 成本 ×N |
| 服务端功能开销 | 消息是不透明字节流,broker 只管搬运 | 每条消息建 ConsumeQueue 索引、可埋轨迹、可过滤 | 每条多几次内存操作与 IO |
注意分区并行这一项不构成差距:RocketMQ 的队列数同样可以水平扩展,混存 CommitLog 的顺序追加也足够快——瓶颈不在"分区能力",而在"每条消息的服务端处理成本"和"无法 sendfile 的消费路径"。
3.4 结论
吞吐量:Kafka > RocketMQ,同语义差距 2.5 倍,最强持久化场景差距 5 倍。
但请务必看清另一半事实:RocketMQ 的 15~32 万 msg/s,对绝大多数业务 Topic 都是天文数字——日均 1 亿条订单消息折算峰值也就 2000/s,离 15 万差两个数量级。吞吐差距只在日志/埋点/CDC 这种"每日 TB 级"的数据链路上才是决定性的。所以"谁快"根本不是业务选型的正确问法——这正是面试官想听的下半句。
四、维度二:功能完备性——RocketMQ 的主场
吞吐输了 2.5 倍,RocketMQ 在这一章连本带利赢回来。四张功能牌,每一张 Kafka 都有"平替",但每一张都要自己搭。
4.1 事务消息:半消息 + 本地事务 + 回查
业务最经典的诉求:"本地事务成功 + 消息一定发出去"要原子。RocketMQ 的解法:
producer.setTransactionListener(new TransactionListener() {
// 第一步:发半消息(消费者不可见)→ 再执行本地事务
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
orderDao.createAndDeductStock((Order) arg); // 本地事务
return LocalTransactionState.COMMIT_MESSAGE; // 成功 → 消息可见
}
// 第二步:若迟迟没收到确认,Broker 主动回查
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
return orderDao.exists(msg.getKeys())
? LocalTransactionState.COMMIT_MESSAGE // 事务其实成功了 → 补提交
: LocalTransactionState.ROLLBACK_MESSAGE; // 确实失败 → 丢弃半消息
}
});
Kafka 的事务是什么?是流处理 exactly-once(read-process-write 幂等),解决的是"消费-加工-再生产"不出重复,做不了"DB 落库 + 发消息"的业务原子性。
| RocketMQ 事务消息 | Kafka 事务 | |
|---|---|---|
| 解决的问题 | 本地事务与发消息的原子性 | 流处理端到端 exactly-once |
| 机制 | 半消息 + 确认 + 回查 | PID + epoch + two-phase commit |
| 典型场景 | 下单扣库存 + 发事件 | Flink 状态后端 checkpoint |
| Kafka 的平替 | 本地消息表 / Outbox 模式 + 扫描补偿 | ——(它本来就是干这个的) |
Kafka 不是做不了业务原子性——Outbox 模式是成熟方案——但每个业务都要建一张消息表、写一个扫描补偿任务、处理补偿的幂等,这是持续性的开发与心智成本。
4.2 延迟/定时消息:原生一行
订单 30 分钟未支付自动关闭,是电商铁打的刚需。RocketMQ:
// 4.x:18 个固定等级(1s/5s/10s/30s/1m...2h)
message.setDelayTimeLevel(16); // 30 分钟
// 5.x:任意时刻,秒级精度
message.setDelayTimeSec(1800); // 1800 秒后投递
Kafka 没有原生延迟消息,平替方案都不优雅:
| 平替方案 | 代价 |
|---|---|
| DB 扫表(状态 + 超时时间) | 扫描频率与时效性矛盾,大表扫描压力大 |
| 多级延迟 Topic(delay-1m/5m/30m)+ 转发 | Topic 数量膨胀,要自己维护转发服务 |
| 独立调度系统(xxl-job 等) | 多一个中间件,消息与调度状态两处管 |
4.3 消息轨迹:全链路可查
开启 msgTrace 后,一条消息从**生产(谁发的、耗时多少)→ 存储(落在哪、什么状态)→ 消费(谁消费的、重试几次、何时 ACK)**全程留痕,控制台一查到底。线上排查"消息到底发出去没/消费了没",从翻各服务日志拼接链路,变成一次查询。
Kafka 平替:客户端埋点 + 统一 trace 串联(可用《Kafka 运维必备工具清单》里的工具辅助)。能做,但埋点代码分散在生产端、消费端、业务代码三处,且没有现成的可视化界面——轨迹是典型的"自建要花一百人力,内建只要一个开关"的功能。
4.4 重试与死信:内建全家桶
RocketMQ 消费失败的处理是全自动的:
消费返回 RECONSUME_LATER
↓ 自动投递到系统重试 Topic:%RETRY%{consumerGroup}
↓ 16 级延迟递增退避:10s → 30s → 1m → ... → 2h
↓ 重试 16 次仍失败
→ 自动进入死信 Topic:%DLQ%{consumerGroup},控制台可视化管理
Kafka 没有内建重试队列(顺序消费场景的坑在《Kafka 顺序消息实战》8.5 详细拆过)。Spring Kafka 的 @RetryableTopic 是最接近的平替:
@RetryableTopic(attempts = "5", backoff = @Backoff(delay = 1000, multiplier = 2))
@KafkaListener(topics = "order-events")
public void handle(OrderEvent event) { ... }
// 背后自建 retry-0..4 + dlt 一串内部 Topic,运维和监控都自己兜
4.5 结论
功能完备性:RocketMQ 完胜。四张牌在 Kafka 里都有技术平替,但每一张都是"自建组件 + 持续运维",而 RocketMQ 是"配置项 + 控制台"。业务消息选 RocketMQ,本质是花钱买现成——吞吐上让出的 2.5 倍,换来的是团队不用造这四个轮子。
五、维度三:延迟与消费体验
5.1 push 与 pull 的体验差异
| Kafka(pull) | RocketMQ(push 长轮询) | |
|---|---|---|
| 消费模型 | 客户端循环 poll,fetch.min.bytes / fetch.max.wait.ms 自己调 | 客户端注册 Listener,服务端推 |
| 低流量延迟 | 调不好会带出"poll 间隔级"延迟 | 开箱即低延迟 |
| 高吞吐延迟 | 批量生效后更稳 | P99 抬升更早 |
| 心智负担 | 客户端参数多(攒批/拉取/位点) | 参数少,服务端代办 |
5.2 实测
| 场景 | Kafka P99 | RocketMQ P99 |
|---|---|---|
| 低流量(1 万 msg/s) | 12 ms(poll 调优后) | 6 ms |
| 高流量(50 万 msg/s) | 15 ms(批量兜住了) | 28 ms |
两个场景各有胜者:低流量下 RocketMQ 的 push 更快(没有 poll 周期的取整损耗),高吞吐下 Kafka 的攒批更稳(延迟换吞吐的收益兑现)。对绝大多数业务——P99 都在 30ms 以内——延迟不构成选型分水岭。
5.3 结论
延迟与消费体验:日常业务两者都合格;RocketMQ 上手更省心,Kafka 高吞吐下更稳,Kafka 的延迟调优(攒批与拉取参数)依赖对《Kafka 为什么这么快》那套机制的理解。
六、不能互相替代的能力:各 3 张底牌
这是面试的决胜题,也是选型的最终依据:各自有对方很难甚至无法补齐的能力。
6.1 Kafka 独有的 3 张底牌
① 大数据生态的总线地位
Flink、Spark Streaming、Debezium(CDC)、Kafka Connect、Schema Registry、ksqlDB……整个数据基础设施默认 Kafka 是入口。RocketMQ 有 connector 生态,但接入一个新组件通常要桥接层,而 Kafka 是"原生第一公民"。这不是性能差距,是十年生态积累的位差,短期内无解。
② 日志模型 + 任意位点回放
Kafka 本质是"可订阅的分布式日志":数据保留期可以设到月级,消费者位点自己管理,随时 --reset-offsets --to-datetime 回放到任意时刻——数仓重算、新服务冷启动订阅三个月历史数据、故障后重新消费,都是日常操作。RocketMQ 也能重置位点,但它定位是"队列",默认保留 48~72 小时,长期存储成本高。"旧账随时能翻"是数据管道的刚需,是业务队列的奢侈品。
③ 高吞吐下的单位成本
按 3.2 实测折算:同样扛 100 万 msg/s,Kafka 约 3~4 台机器,RocketMQ 同规格要 8~10 台。日志/埋点这种"条数巨大、单条价值低"的流量,成本差就是选型本身。
6.2 RocketMQ 独有的 3 张底牌
① 事务消息
"本地事务 + 发消息"的原子性,Kafka 没有对等物(它的事务是流处理语义)。Outbox 平替可行,但每个业务都要自建补偿与幂等——交易链路里这是核心能力,不是可选项。
② 延迟/定时消息
原生、精确、一行代码。Kafka 的所有平替(扫表/多级 Topic/外挂调度)都比它重一个数量级。关单、超时重试、预约提醒……凡是"到点才发生"的业务,这一张牌就够把天平压过去。
③ 轨迹 + 重试 + 死信的服务端全家桶
排查有轨迹、失败有退避、耗尽有死信、控制台可视化管理。Kafka 要拼出同等能力,需要 trace 埋点 + @RetryableTopic + 死信监控 + 自建管控台四件套,且长期维护成本摊在每个人身上。
6.3 底牌对照表
| 能力 | Kafka | RocketMQ | 补齐成本 |
|---|---|---|---|
| 大数据生态位 | 原生 | 需桥接 | 每个组件一次桥接,持续维护 |
| 位点回放/长期存储 | 月级保留 + 时间旅行 | 短保留,重放受限 | 换存储或外挂归档 |
| 高吞吐单位成本 | 3~4 台 / 百万 msg/s | 8~10 台 / 百万 msg/s | 直接的机器账 |
| 事务消息 | Outbox 自建 | 原生 | 每业务一张表 + 补偿任务 |
| 延迟消息 | 自建三选一 | 原生一行 | 一个独立调度组件 |
| 轨迹/重试/死信 | 四件套自建 | 原生全家桶 | 埋点 + 管控台 + 长期运维 |
七、总结
星级对比表
| 维度 | Kafka | RocketMQ |
|---|---|---|
| 极限吞吐 | ★★★★★ | ★★★ |
| 高吞吐单位成本 | ★★★★★ | ★★★ |
| 开箱延迟 | ★★★★ | ★★★★★ |
| 事务消息 | ★★ | ★★★★★ |
| 延迟/定时消息 | ★ | ★★★★★ |
| 消息轨迹 | ★★ | ★★★★★ |
| 重试与死信 | ★★ | ★★★★★ |
| 大数据/流生态 | ★★★★★ | ★★ |
| 位点回放/长期存储 | ★★★★★ | ★★★ |
| 顺序消息 | ★★★★ | ★★★★ |
| 运维友好度 | ★★★★ | ★★★★ |
(顺序消息两者打平:Kafka 分区键 hash,RocketMQ MessageQueueSelector 显式路由,细节见《Kafka 顺序消息实战》;KRaft 之后 Kafka 运维复杂度大幅缓解,两者打平。)
各自适用场景
| 场景 | 选谁 | 一句话理由 |
|---|---|---|
| 日志/埋点/用户行为采集 | Kafka | 吞吐 + 成本 + 生态 |
| CDC 数据管道(MySQL→数仓) | Kafka | Debezium 原生对接 |
| Flink/Spark 流处理 | Kafka | 默认数据总线 |
| 需要回放重算的链路 | Kafka | 位点自由 |
| 订单/支付/库存交易消息 | RocketMQ | 事务 + 轨迹 + 重试 |
| 关单/超时提醒/定时触发 | RocketMQ | 原生延迟消息 |
| 金融级强持久化业务 | RocketMQ | 同步刷盘可选 |
| 中小团队业务解耦 | RocketMQ | 开箱即用,心智负担小 |
选型决策树
新系统要上消息队列,怎么选?
├── 流量是"数据"(日志/埋点/CDC/流处理/数仓管道)
│ └── Kafka,不用纠结
├── 流量是"业务"(交易/订单/状态变更/异步任务)
│ ├── 需要事务原子性 / 延迟投递 / 轨迹审计 → RocketMQ
│ └── 只要可靠 pub-sub,且团队已有成熟 Kafka 体系
│ └── Kafka 也能干,接受 Outbox/重试/轨迹的自建成本
├── 两者都要?→ 主流大厂方案:按消息角色分,不按"谁快"分
│ └── 数据链路 → Kafka;业务链路 → RocketMQ
└── 团队只有一个中间件 headcount
└── 少即是多:一个 MQ 吃透,好过两个都半吊子
面试标准答案(背这四句)
- 纠偏:"这个问题的前提需要修正——Kafka 和 RocketMQ 定位不同:前者是分布式日志系统/数据管道,后者是业务消息中间件,不是同一赛道的快慢之分。"
- 快的原因:"Kafka 快在零拷贝、客户端攒批、分区并行,根源是'每分区独立日志'的存储模型让数据可以不进用户态,走 sendfile 直达网卡。"
- 为什么 RocketMQ 不抄:"因为它的功能定位要求服务端解析每条消息——事务回查、延迟调度、过滤、轨迹都建立在数据进用户态之上,sendfile 就失效了。这是功能定位的结构性代价,不是技术能力差距。"
- 结论:"高吞吐数据链路选 Kafka,业务交易链路选 RocketMQ,两者在实践中是并存互补,不是二选一。"
一句话
Kafka 和 RocketMQ 的性能差距,是同一个架构分歧的两面:Kafka 把消息当"不透明的字节流"存——每分区一个日志文件、broker 只管顺序写和按区间读,才换来 sendfile 零拷贝、客户端攒批和 2.5~5 倍的吞吐,代价是功能全推给客户端和生态组件;RocketMQ 把消息当"一等公民对象"存——单 CommitLog 混存、broker 解析每条消息,才换来事务回查、延迟调度、轨迹与重试的全家桶,代价是数据必须进用户态、吞吐让出 2.5 倍——所以面试答"零拷贝+批量+分区并行"只值一半分,另一半在"为什么 RocketMQ 不抄过去":不是不能,是它的存储模型从第一天就选择了另一条路;选型亦然——高吞吐的数据链路交给 Kafka,功能敏感的业务链路交给 RocketMQ,让账本归账本、挂号机归挂号机。
关键数据
- 同环境同语义实测:Kafka 80 万 msg/s vs RocketMQ 32 万(异步刷盘),差距约 2.5 倍;同步刷盘 15 万,差距约 5 倍
- 但业务日均 1 亿条消息峰值仅约 2000/s,离 RocketMQ 上限差两个数量级——吞吐差距在业务场景不构成痛点
- 低流量延迟:RocketMQ push P99 6ms vs Kafka 12ms;高流量:Kafka 15ms vs RocketMQ 28ms——各有胜场
- 事务消息平替成本:每业务一张本地消息表 + 扫描补偿任务 + 幂等处理
- 100 万 msg/s 的机器账:Kafka 约 3~4 台 vs RocketMQ 约 8~10 台——大数据链路的成本差就是选型
给团队的建议
| 项目状态 | 建议 |
|---|---|
| 只有一个 MQ 的团队 | 按消息角色选型后再统一,别让一个 MQ 既扛日志又扛交易 |
| 双 MQ 并存的团队 | 明确边界:数据 Topic 归 Kafka、业务 Topic 归 RocketMQ,写进架构规范 |
| 已全司 Kafka | 业务消息硬用可以,Outbox/重试/轨迹的自建成本立项算清,别温水煮青蛙 |
| 已全司 RocketMQ | 日志/埋点/CDC 回流 Kafka,别让交易中间件硬扛 TB 级数据流 |
| 面试准备 | 把"为什么不抄零拷贝"这个追问练熟——它同时考架构、存储模型和权衡思维 |
| 技术选型评审 | 用本文 6.3 底牌对照表逐项过,而不是凭"听说谁快" |
八、常见问题
8.1 RocketMQ 5.x 不是做了很多性能优化吗?还慢吗?
5.x 的存储 KV 分离和 Proxy 模式确实把写入和消费性能拉高了一截,我们同环境复测异步刷盘从 25 万提到 32 万。但定位没变:它依然是"服务端解析消息"的架构,事务/延迟/轨迹/过滤这些功能还是要碰消息内容,sendfile 的路依然走不通——优化缩小的是工程差距,抹不掉的是架构分歧。这反而是答题的好素材:说明你关注版本演进,而不是背三年前的结论。
8.2 我们全司只有 Kafka,业务消息硬用它行不行?
行,但要把四个自建组件的账算清:① 事务原子性 → 每业务一张本地消息表 + 扫描补偿;② 延迟消息 → 扫表或多级延迟 Topic;③ 轨迹 → 客户端埋点 + trace 串联;④ 重试死信 → @RetryableTopic + 死信监控。四个轮子都能造,问题是每个业务都要重复造一遍,且各团队的造法不一。如果业务复杂度到了交易级,引入 RocketMQ 的一次性成本通常低于持续自建的累计成本——按"自建人力 × 业务数"算这笔账。
8.3 为什么 RocketMQ 不把存储改成 Kafka 那样的分区日志,直接抄零拷贝?
改了,服务端功能就没了。轨迹、延迟队列、事务回查、Tag/SQL92 过滤,全都依赖"broker 能拿到并解析全量消息"——单 CommitLog 混存正是为此服务的。5.x 的 KV 分离算是一次折中尝试(索引与消息体分离存储),但"数据不进用户态"和"服务端看得见数据"在原理上互斥。这道题的精髓是:架构没有免费午餐,每一分性能都对应一种功能上的放弃。
8.4 Kafka 事务和 RocketMQ 事务到底差在哪?
完全不同的两个东西。Kafka 事务 = 流处理 exactly-once:用 PID + epoch 保证"消费-加工-生产"链路不重不丢,服务的是 Flink 这类流式计算。RocketMQ 事务 = 业务分布式事务的一半:半消息 + 本地事务 + 回查补偿,服务的是"DB 操作和发消息要么都成要么都不成"。一句话区分:Kafka 保证数据流不出错,RocketMQ 保证业务动作原子。
8.5 那 Pulsar 呢?存算分离不是两边都想要吗?
Pulsar 的确用分层架构(Broker 无状态 + BookKeeper 存储层)兼顾了功能与弹性,云原生场景有优势。但它多一层组件(ZooKeeper + BookKeeper + Broker),运维复杂度最高,国内生产渗透率远低于两者。我的建议:主战场在 Kafka/RocketMQ 之间选,Pulsar 留给云原生 k8s 深度玩家——别为了"看起来先进"引入第三个 MQ。
8.6 顺序消息两者谁做得更好?
打平,但实现路线有趣地不同:Kafka 靠分区键 hash(同实体同分区 + 分区串行消费,《Kafka 顺序消息实战》有完整三板斧),RocketMQ 靠 MessageQueueSelector——路由选择权交给业务代码。这带来一个隐藏差异:Kafka 的 murmur2 硬 hash 遇到扩分区会漂移(《Kafka 顺序消息实战》2.4),RocketMQ 可以在业务层维护 shardingKey→queue 的稳定映射来规避,灵活性略胜。当然前提是你愿意维护这张映射表。
互动话题:你们公司是 Kafka、RocketMQ 还是双 MQ 并存?有没有"用 Kafka 硬扛业务消息然后不停补轮子"或"用 RocketMQ 硬扛数据流被吞吐教做人"的经历?评论区聊聊你们的选型故事,点赞最高的场景我拆成下一篇的完整实战案例。
参考资料
- Kafka 官方设计文档:https://kafka.apache.org/documentation/#design
- RocketMQ 官方文档(架构/事务/延迟消息):https://rocketmq.apache.org/docs/
- RocketMQ 5.x 存储优化说明:https://rocketmq.apache.org/docs/quickStart/05QuickStart
- KIP-500:KRaft 取代 ZooKeeper:https://cwiki.apache.org/confluence/display/KAFKA/KIP-500
- 本系列前作:《Kafka 为什么这么快?——顺序写+页缓存+零拷贝+批量,四板斧拆解》
- 本系列前作:《Kafka 消息不丢失实战:生产者ack+副本ISR+消费者手动提交的完整配置》
- 本系列前作:《Kafka 顺序消息实战:同一个订单的消息必须按序消费——分区键设计+单分区单消费者》
- 本系列前作:《Kafka 运维必备工具清单:这5个工具让排障快10倍》
标题:面试官:Kafka 为什么比 RocketMQ 快?——不是快,是场景不同
作者:jiangyi
地址:http://jiangyi.space/articles/2026/10/24/1792337139939.html
公众号:服务端技术精选
- 引言
- 一、两位选手简介
- 1.1 Kafka
- 1.2 RocketMQ
- 1.3 一句话画像
- 二、架构对比:两张图看懂"快"与"全"的分歧
- 2.1 两张架构图
- 2.2 关键差异表
- 2.3 追问的正解:为什么 RocketMQ 不抄零拷贝?
- 三、维度一:吞吐量——Kafka 的主场
- 3.1 测试环境
- 3.2 测试结果
- 3.3 数据分析
- 3.4 结论
- 四、维度二:功能完备性——RocketMQ 的主场
- 4.1 事务消息:半消息 + 本地事务 + 回查
- 4.2 延迟/定时消息:原生一行
- 4.3 消息轨迹:全链路可查
- 4.4 重试与死信:内建全家桶
- 4.5 结论
- 五、维度三:延迟与消费体验
- 5.1 push 与 pull 的体验差异
- 5.2 实测
- 5.3 结论
- 六、不能互相替代的能力:各 3 张底牌
- 6.1 Kafka 独有的 3 张底牌
- 6.2 RocketMQ 独有的 3 张底牌
- 6.3 底牌对照表
- 七、总结
- 星级对比表
- 各自适用场景
- 选型决策树
- 面试标准答案(背这四句)
- 一句话
- 关键数据
- 给团队的建议
- 八、常见问题
- 8.1 RocketMQ 5.x 不是做了很多性能优化吗?还慢吗?
- 8.2 我们全司只有 Kafka,业务消息硬用它行不行?
- 8.3 为什么 RocketMQ 不把存储改成 Kafka 那样的分区日志,直接抄零拷贝?
- 8.4 Kafka 事务和 RocketMQ 事务到底差在哪?
- 8.5 那 Pulsar 呢?存算分离不是两边都想要吗?
- 8.6 顺序消息两者谁做得更好?
- 参考资料
评论