
经常有人问我RocketMQ 和 Kafka 到底选哪个为什么大家都说 RocketMQ 性能不如 Kafka这个问题几乎每隔一阵子就会出现一次尤其在做技术选型、面试复盘、或者线上消息积压排查的时候。问的人多了我决定干脆把这几年压测、生产运维、以及源码阅读里看到的差异完整写下来。这篇文章不打算给你一个“谁吊打谁”的结论而是把 RocketMQ 和 Kafka 的架构差异、性能差异的根源、以及在真实业务里该怎么选拆开讲清楚。适合谁来读正在做消息中间件选型的开发/架构师、准备 MQ 相关面试的人、以及那些已经在用其中一款、但想搞明白“为什么另一款更快”的运维和开发同学。1. 先把问题问清楚哪个性能在什么场景下1.1 性能是多维度的先说吞吐和延迟“RocketMQ 性能不如 Kafka”这种说法默认指的都是吞吐量。Kafka 以极高的写入吞吐著称几百万条/秒的消息写入在一些压测里都能跑出来而 RocketMQ 的官方宣传虽然也很猛但实际压测里单 Broker 的写入 TPS 通常比 Kafka 低一截。这个结论在吞吐维度上大体成立。但性能从来不是一个单一的指标。除了吞吐还有延迟、堆积能力、消费性能、资源消耗、集群扩展性。如果你把维度切换到“端到端延迟稳定性”或者“消息在极端堆积下的消费吞吐”结果并不一定是 Kafka 全面胜出。很多人没意识到一个问题Kafka 的高吞吐是拿“功能简单”换来的RocketMQ 的相对低吞吐是拿“功能丰富”换来的。两者不是同一类东西。Kafka 本质上是一个分布式提交日志它把消息当作不断追加的日志流RocketMQ 则是一套面向业务消息场景的消息中间件它做了大量 Kafka 没有的功能比如事务消息、定时消息、死信队列、消息过滤、消费重试。你拿一个“只做了日志追加”的系统和“带了一大堆扩展功能”的系统比吞吐本质上是在拿跑车和越野车比直线加速。1.2 我实际压到过的量级仅供参考先给一组我自己的压测参考数据不代表绝对标准但能帮你建立量级感知。环境三台物理机部署单集群磁盘是 NVMe SSD内网万兆消息大小 1KB单 Topic分区数拉到 32生产端 ack 策略按各自最常用的配置来。Kafka3 版本左右acks1batch 默认偏大写 TPS 大概在 80 万~120 万之间P99 延迟约 20~50ms。RocketMQ4.9 版本异步刷盘、异步复制、单发单条写 TPS 在 25 万~40 万之间P99 延迟约 5~15ms。如果把 Kafka 的 acks 改成 all等 ISR 全部确认性能会明显下降RocketMQ 如果改成同步刷盘同步复制同样也会掉不少。这个测试不是严格的对照实验因为 Kafka 走了批量发送RocketMQ 走的是单条发送。但它基本反映了一个常见认知Kafka 在极限吞吐上确实有优势而且优势还不小。但在延迟方面RocketMQ 在单条发送场景下反而容易做得更低因为 Kafka 为了吞吐需要攒批攒批就意味着引入等待时间。注意我这里说的“延迟更低”是指同步单条发送的响应延迟。如果业务用 Kafka 也配置成单条发送、linger.ms0那延迟也可以很低但吞吐优势就没那么明显了。1.3 性能差多少要看三个变量真实环境里两个系统的性能差距不是一个固定倍数它至少受三个变量影响。第一个变量是消息大小。消息越大磁盘 IO 和网络带宽越容易成为瓶颈两者差距越小。1KB 小消息下 Kafka 优势明显因为小消息考验的是并发、批处理、元数据管理的效率但如果消息是 100KB 甚至 1MB纯追加日志的 Kafka 和带索引的 RocketMQ 在写入路径上都会受限于磁盘吞吐差距就没那么夸张了。第二个变量是分区数/队列数。Kafka 的高吞吐高度依赖多分区并行但分区数太多会带来文件句柄、选主、元数据同步的开销RocketMQ 则完全相反队列数越多ConsumeQueue 的小 IO 和消费随机读问题越严重。所以“多队列高并发”场景下Kafka 的优势会被进一步放大。第三个变量是刷盘和复制策略。Kafka 的消息落盘本质上是写到操作系统的 PageCache再异步刷盘RocketMQ 默认也是异步刷盘但它多了一个构建 ConsumeQueue 索引的步骤。也就是说在同样的磁盘条件下RocketMQ 每写一条消息做的事比 Kafka 多。这一点后面会详细拆。所以当你听到“RocketMQ 性能不如 Kafka”这句话时先反问一句是什么消息大小、什么配置、什么维度下的对比没有这些前提结论没有意义。2. 从根本上理解两条存储路线Kafka 为什么能快2.1 Kafka分区日志写和读都是顺序的Kafka 最核心的设计思路是把消息当作日志文件来管理。一个 Topic 被拆成多个 Partition每个 Partition 在物理上是一个独立的日志目录里面是一组按顺序追加的 Segment 文件。生产者写入消息时按照 key 哈希或者轮询把消息追加到对应 Partition 日志文件的尾部。这个过程是典型的海量顺序写。顺序写有多重要普通机械硬盘的随机写 IOPS 只有几百但顺序写可以跑到 100MB/s 以上的吞吐即使换成 SSD/NVMe顺序写的吞吐和随机写也有数量级差距。Kafka 等于把这个优势吃满了——只要消息是追加写入的它就能持续用最高的磁盘性能。更关键的是Kafka 的消费读取也是顺序的。每个消费者组里的消费者按照 offset 从 Partition 日志里从头往后读。这个读取路径是线性的、连续的磁盘预读和 PageCache 都能很好地发挥作用。同样是一个日志系统你在写入端做到顺序写不难难的是在消费端还能保持顺序读。Kafka 用“每个分区独立文件”的模型很自然地把这两个顺序合到一起了。2.2 页缓存、批量发送和零拷贝Kafka 的高吞吐不光是靠顺序写还靠以下三个机制PageCacheKafka 写入消息时并没有直接调用 fsync 把数据刷到磁盘而是先落到操作系统的 PageCache 里由操作系统择机刷盘。这意味着写入操作大部分时候只是在写内存缓存速度极快。消费的时候如果数据还在 PageCache 里直接读内存根本不碰磁盘。Kafka 整个链路对 PageCache 的依赖程度极高这是它吞吐爆表的基础。批量发送Kafka 生产端有 batch.size 和 linger.ms 两个参数。生产者会把发往同一分区的多条消息攒成一批然后一次性发送。批量发送不仅减少了网络请求次数还让每批消息在服务端可以连续写入压缩效率也更高。配合 lz4 或 zstd 压缩小消息场景下的吞吐提升非常夸张。零拷贝传统的数据读取路径是磁盘 → 内核缓冲区 → 用户程序缓冲区 → 内核 socket 缓冲区 → 网卡。数据在用户态和内核态之间要复制好几次。Kafka 在向消费者传输消息时使用 sendfile 系统调用让数据直接从 PageCache 经 DMA 拷贝到网卡省去用户态的中转。虽然实际消费时还需要解压和解析消息但底层传输路径确实省了不少拷贝开销。这三个机制叠加让 Kafka 的写入和消费都保持了非常高的效率。很多团队选 Kafka 做日志采集和数据管道就是看中它在“数据需要极高的吞吐、可容忍一定延迟”的场景下几乎是无敌的存在。2.3 分区并行Kafka 性能的另一半秘密如果不谈分区Kafka 的吞吐优势会打一个很大折扣。单分区 Kafka 的顺序写性能虽然不错但跟 RocketMQ 单队列比未必有决定性优势。真正的区别在于Kafka 能把负载分散到 N 个分区上同时每个分区又有独立的副本和 leader。生产端可以按分区并行发送服务端多个分区文件可以并行追加消费端一个分区对应一个消费线程天然支持水平扩展。你在压测 Kafaka 时会发现分区数从 1 加到 32吞吐能线性增长好几倍。同一个 Topic 的分区数越多吞吐上限越高。当然分区数太多也有代价每个分区都有日志文件、索引文件、副本同步对文件句柄、元数据管理、内存都会有压力。生产环境里分区数一般控制在 3 的倍数配合节点数来设置不能盲目贪多。但不管怎么说分区并行 顺序 IO就是 Kafka 高吞吐的底层逻辑。这跟 RocketMQ 的存储模型形成了鲜明对比。3. RocketMQ 的 CommitLog ConsumeQueue功能强大了但代价藏在消费路径里3.1 双写结构的初衷RocketMQ 的存储模型比 Kafka 多了一层索引叫 ConsumeQueue。RocketMQ 里所有 Topic 的消息都先写到一个共享的 CommitLog 文件中CommitLog 按 1GB 大小切分成多个文件顺序追加写入。也就是说RocketMQ 在写入端也是顺序写这一点并不输 Kafka。但问题出在索引上。RocketMQ 为了让每个 Topic/队列能独立消费需要根据 CommitLog 里的消息异步构建出 ConsumeQueue。每个 ConsumeQueue 文件对应一个队列里面存储的是消息在 CommitLog 中的物理偏移量、消息大小、Tag Hash 值。每个条目固定 20 字节。当消费者要拉取某个队列的消息时流程是先读 ConsumeQueue 拿到消息在 CommitLog 里的 offset再根据这个 offset 去 CommitLog 物理读取消息内容。这个“先查索引再读数据”的模型就是 RocketMQ 和 Kafka 最大的性能分水岭。3.2 消费时的随机读问题Kafka 每个分区有独立的日志文件消息写入和消费都按顺序进行。RocketMQ 呢所有 Topic 消息共享一个 CommitLog虽然写是顺序的但不同队列的消息在 CommitLog 里是交错存放的。假设你有 Topic A 的队列 1 和队列 2生产者轮询往两个队列写消息。CommitLog 里的消息排列是队列1的消息、队列2的消息、队列1的消息、队列2的消息……在物理文件上它们并不连续。当你消费队列1时实际上是在 CommitLog 的多个位置间跳来跳去读数据也就是随机读。随着 Topic 数和队列数增加这种随机读问题会被放大。这也是为什么 RocketMQ 官方文档和一些实践都提示单个实例的 Topic 数不宜过多队列数也要控制在一定范围。一旦 Topic 数过多ConsumeQueue 占用、随机读、索引更新的开销都会上涨性能会明显下滑。Kafka 则没有这个问题分区数多带来的主要是文件句柄和选主压力消费读取依然是按分区顺序读。所以在“大量 Topic、大量队列”的场景下Kafka 对 RocketMQ 的吞吐优势会被进一步拉大。3.3 索引、过滤、事务、重试每一层功能都在消耗性能除了 ConsumeQueueRocketMQ 还有一些额外开销每一个 Kafka 默认都不需要承担。IndexFile 索引RocketMQ 会为每条消息构建一个用于消息查询的索引文件key 查消息、时间区间查消息。这个索引是在写消息时同步或异步构建的增加了 CPU 和 IO 开销。Kafka 只有一个比较轻量的稀疏索引主要用于 offset 定位功能远没有 RocketMQ 的索引强大。消息过滤RocketMQ 支持基于 Tag 和 SQL92 表达式的过滤。基础 Tag 过滤还好如果用了 SQL 过滤Broker 在消费端还需要逐条解析消息属性CPU 开销明显上升。Kafka 的过滤能力很弱基本靠消费者自己处理Broker 层面不做这种计算。事务消息和定时消息RocketMQ 的事务消息需要先把 half 消息写入专门的 topic再根据事务状态决定提交或回滚定时消息则会把消息写入 SCHEDULE_TOPIC_XXXX等到时间到了再转投到真实 Topic。这两个功能都意味着消息会被写入多次。Kafka 原生是不支持事务消息和定时消息的它没有这些额外写入。消费重试和死信队列RocketMQ 消费失败后消息会自动进入重试队列重试次数超过阈值会进入死信队列。这背后是对消息进行重新投递、消费点位管理的一系列逻辑。Kafka 默认没有自动重试队列失败的消息由消费者自己处理。每一条功能在单条消息的链路上都是额外的计算。单看一次写入RocketMQ 可能只是比 Kafka 多几微秒的索引开销但当 TPS 升到几十万甚至上百万时这些“微不足道的多几微秒”就会累积成明显的吞吐差距。你可以理解为RocketMQ 每处理一条消息身上就多背了几件装备跑起来当然会比一身轻装的 Kafka 吃力。4. 为什么“RocketMQ 性能不如 Kafka”这个结论经常是配置不公造成的4.1 默认配置对比其实是田忌赛马网上很多对比测试直接拿两个中间件的默认配置跑然后得出一个数字。这个做法问题很大。RocketMQ 的默认配置里有些场景是为了数据安全而牺牲性能的。比如 broker 的 flushDiskType 默认是 ASYNC_FLUSH异步刷盘这个还好但很多人测试时并不清楚同步刷盘和异步刷盘的区别。如果你跑到生产集群把 RocketMQ 配成 SYNC_FLUSH同步刷盘 主从同步复制而 Kafka 还跑在默认的异步 PageCache 刷盘上这个对比就完全不公平了。正确做法是先拉平语义。你要比“同等的可靠性等级”下的性能——两边都要求消息确认后不丢那就该让 Kafka 开 acksall让 RocketMQ 开同步刷盘同步复制两边都允许极端情况下丢一点消息、只追求最快速度那就让两边都跑在异步模式。把可靠性等级拉到同一水平再谈性能对比。4.2 ack、刷盘、复制同一套语义才能对比这里我列一个对照表方便你理解两个系统的可靠性语义怎么对齐可靠性/一致性要求RocketMQ 配置Kafka 配置最快速度、容忍少量丢失刷盘丢AsyncFlush异步刷盘producer acks0/1依赖 PageCache 异步刷盘写入主节点即算成功AsyncFlush 主从异步复制acks1写入主节点并同步到副本才算成功AsyncFlush 主从同步复制acksall结合 min.insync.replicas刷盘成功才算成功SyncFlush同步刷盘producer acksall log.flush.interval.messages 调低实际上 Kafka 很少用同步刷盘来保数据如果你拿“RocketMQ 同步双写”对比“Kafka 异步发送”那结果没有任何参考价值。反过来也一样拿“Kafka 副本同步”去比“RocketMQ 异步刷盘”同样不公平。4.3 一个容易误导人的压测场景还有一个常见的坑只测“生产端写入”不测“消费端读取”。很多 benchmark 工具只统计生产的 TPS因为消费端的性能更难测。但生产环境里消息最终是要被消费掉的消费路径上的差异才是 RocketMQ 和 Kafka 真正的差距所在。比如在消息积压场景下Kafka 消费端按分区顺序读日志积压越多它的顺序读优势越明显消费吞吐能保持稳定RocketMQ 消费端要先读 ConsumeQueue再按 offset 去 CommitLog 随机读积压大了之后如果 PageCache 命中率下降消费吞吐就会明显波动。所以如果你要在自己公司做选型对比我建议至少测三组生产端写入性能纯生产测试不同 ack 策略各测一遍消费端消费性能积压 100 万~1000 万条后起 N 个消费者并发消费看消费 TPS端到端延迟生产到消费的全链路 P99观察有没有长尾。只测其中任何一组都可能得出片面的结论。5. 抛开性能这两者在生产里其实各管一段5.1 Kafka 适合的三种场景第一类是日志与埋点采集。客户端产生的访问日志、点击流、设备上报数据量大、允许丢失、不需要复杂的事务能力。Kafka 的高吞吐和顺序读非常适合做这种海量数据的第一站。第二类是数据管道。把数据库的变更事件、业务系统的增量数据源源不断同步到数仓、数据湖、ES、Flink 等下游。Kafka 的生态在这里优势太大了——Connect、Schema Registry、流处理框架都和它深度集成你用 Kafka 做数据中枢几乎是标配。第三类是流处理应用。如果你在做实时计算、实时风控、实时大屏上游是 Kafka下游接 Flink/Spark Streaming这是最成熟的组合。Kafka Streams 也能直接做一部分流处理逻辑生态完善。5.2 RocketMQ 适合的三种场景第一类是核心交易链路。订单创建、支付回调、库存扣减这类消息要求不能丢、不能重复投递或至少能幂等处理、支持事务。RocketMQ 的事务消息是它最核心的杀手锏本地事务和消息发送绑在一起通过半消息和事务回查机制能把最终一致性做得比较优雅。Kafka 虽然也有事务 API但用起来复杂得多在生产实践中很少有人拿 Kafka 事务去支撑交易链路。第二类是需要延迟投递和定时消息的业务。比如订单 15 分钟未支付自动关闭、会员到期提醒、定时任务触发。RocketMQ 的延迟消息是开箱即用的Kafka 需要自己实现一套定时机制。你可能觉得“这不难”但真正做起来要处理服务重启、时间轮、持久化等问题工作量不小。第三类是面向业务团队的多 Topic、多消费者、消息重试场景。RocketMQ 的重试队列、死信队列、Tag 过滤、消息轨迹、控制台可视化都是为业务开发人员准备的。业务团队可以比较低成本地上手和排查问题这在很多公司里比那点吞吐差距更重要。5.3 阿里为什么敢在淘宝交易链路用 RocketMQ很多人会问如果 Kafka 性能更好为什么阿里这种体量的公司交易链路不用 Kafka反而自己搞了个 RocketMQ答案不是性能而是业务特性。淘宝的交易消息单消息量并不算极端但要求高可靠、支持事务、支持延迟消息、支持消息轨迹和精细的重试策略。这些能力 Kafka 要么没有要么实现成本太高。RocketMQ 存在的一个核心使命就是把“分布式事务消息”“可靠投递”“业务消息的可观测性”这些能力做成开箱即用的产品。它不追求每秒几百万条的消息吞吐它追求的是在每秒几十万条的体量下把可靠性、可运维性、业务集成度做到极致。这一点理解透了你就不会再用“谁吞吐高谁就赢”来评判这两种中间件了。6. 我的选型建议和踩坑总结6.1 选型判断框架如果你现在要做选型我建议按这个思路来判断先看业务有没有 Kafka 不可替代的生态需求。如果你要接 Flink 做流计算或者要做数据管道、日志采集选 Kafka 基本没悬念。这个场景下 RocketMQ 的生态明显偏弱。再看业务有没有 RocketMQ 独有的功能需求。如果你需要事务消息、延迟消息、丰富的过滤和重试能力而且团队是 Java 系、希望用控制台排查问题那 RocketMQ 是更顺的选择。用 Kafka 去实现同样的功能你会发现自己写了一堆中间件代码。如果两边需求都不明显那就看团队已有基础设施。公司已经有一套成熟的 Kafka 运维体系那就用 Kafka公司已经用了 RocketMQ 且集群稳定那就继续用。迁移的代价远比那一倍两倍的吞吐差异大。6.2 性能对比压测的正确姿势最后分享一组压测时需要注意的细节都是我踩过的坑拉平配置。刷盘、复制、ack、批量大小、单条消息大小、压缩算法这些关键参数必须对齐。对比“RocketMQ 同步复制 vs Kafka acksall”才算同等级对比。选择消息大小要贴合业务。不要只测 1KB 小消息如果你业务主要跑大消息就用业务实际的消息大小去测结论才可信。压测时间要足够长。至少跑 1 小时以上。有些系统短时间跑很猛半小时后因为 JVM GC、文件句柄、PageCache 压力就掉下来了。关注端到端延迟而不是只有 TPS。高吞吐下 P99 可能飙得离谱你要结合自身业务的延迟要求来判断。加一台真实消费者做全链路测试。生产端 TPS 高不代表消费端能跟上消费端跟不上生产端最终还是会被背压限制。6.3 最后几个经验提醒以我自己的实际经验来说很多团队最终不用某个消息队列往往不是因为它性能不够而是因为日常使用中缺了想要的功能或者问题排查太费劲。Kafka 性能确实强悍但它默认不提供消息重试、死信、事务消息这些开箱即用的特性RocketMQ 性能相对低一些但在业务消息场景里它给你省下的开发成本、运维成本通常远超那点吞吐损失。所以遇到“RocketMQ 为什么性能不如 Kafka”这个问题最好的回答不是“确实不行”而是“它把一部分性能换成了别的能力而这些能力在你业务里到底值多少钱才是真正要算的账”。