消息队列三巨头:Kafka vs RocketMQ vs Pulsar——百万级消息吞吐实测

引言

三个月前,我们做了一次选型评审。会议室白板上写着三个候选:Kafka、RocketMQ、Pulsar。两小时讨论,最后停在同一个问题上:

"你们说的'快',是看了谁的博客?"

提案 A 说 Kafka 单机百万;提案 B 说 Pulsar 存算分离、弹性最好;提案 C 说 RocketMQ 双 11 扛过万亿级。三个人引用的都是厂商宣传页和几年前的 PPT,环境不同、消息大小不同、持久化语义不同——没有一个数字能横向比较。

于是我们自己搭了一套环境,把三套集群都按同样的机器规格、同样的消息、同样的可靠性语义压了一遍。3 台 8 核 16G 的机器,各 1000 万条 1KB 消息,每轮跑三遍取中位数。

先给结论(细节和原始数据在后文):

维度第一名第二名第三名一句话
生产吞吐Kafka 26.8 万/s(262 MB/s)Pulsar 14.2 万/sRocketMQ 9.6 万/sKafka 领先 2.8 倍
端到端 P99(稳态)RocketMQ 9msKafka 18msPulsar 42mspush 长轮询占优
积压 1000 万条的消化速度Kafka 55 万/s(182 秒)Pulsar 21 万/sRocketMQ 18 万/s差 3 倍
运维复杂度(越低越好)Kafka(3 进程)RocketMQ(9 进程)Pulsar(9~12 进程)Pulsar 最重
功能完整度Pulsar ★★★★★RocketMQ ★★★★★Kafka ★★★各有侧重

一句话破题:Kafka 是吞吐王,Pulsar 是架构最先进但运维最重的那个,RocketMQ 是综合最均衡的那个——所谓"三巨头"没有全能冠军,只有"你的场景更需要哪一项"。

需要提前说明一点:标题里的"百万级"是生产集群的常见目标量级,本文 3 台小机器实测 Kafka 是 26.8 万 msg/s @1KB,这个数字和网上常见的"百万"不冲突——消息大小、副本数、机器规格差一个档位,绝对数字就能差十倍。第 3.6 节会给出换算与更大规格的复测数据,别拿不同口径的数字互相打架。


一、三巨头简介

1.1 Kafka

出身:2011 年 LinkedIn 开源,为"网站行为日志搬运到 Hadoop"而生,2012 年成为 Apache 顶级项目,2021 年后由 Confluent 主导演进。

定位:分布式提交日志(Distributed Commit Log),流数据平台的事实标准。

特点:

  • 每个分区一个独立日志文件,顺序追加写 + 稀疏索引
  • 四板斧:顺序写 + PageCache + sendfile 零拷贝 + 客户端攒批压缩(详见《Kafka 为什么这么快》)
  • 4.0 起 KRaft 完全取代 ZooKeeper,组件从"3 Broker + 3 ZK"瘦身到"3 Broker"
  • 生态最厚:Flink/Spark/Debezium/Connect/Schema Registry 全部原生对接

现状:大数据与流处理领域不可替代;功能层(事务消息、延迟、轨迹)仍是短板。

# Kafka 的"快"写在客户端配置里
batch.size=65536
linger.ms=5
compression.type=zstd
acks=all
enable.idempotence=true

1.2 RocketMQ

出身:2012 年阿里开源(前身 MetaQ),为双 11 交易链路打造,2016 年进入 Apache。

定位:业务消息中间件,交易场景的功能完备性优先。

特点:

  • 所有队列消息混存单个 CommitLog,ConsumeQueue 只存索引(架构分歧的根源,见 1.4)
  • 四张功能牌:事务消息、延迟/定时消息、消息轨迹、重试与死信,全部服务端内建
  • NameServer 无状态,组件轻但路由感知有秒级延迟
  • 5.x 引入 Proxy 模式与 gRPC 多语言支持

现状:国内金融、电商渗透率极高;纯吞吐场景打不过 Kafka,但业务功能的"开箱即用"是硬优势。

message.setDelayTimeSec(1800);                  // 延迟消息,一行
producer.sendMessageInTransaction(msg, order);  // 事务消息,一行

1.3 Pulsar

出身:2016 年 Yahoo 开源,2018 年 Apache 顶级项目,现由 StreamNative 主导。

定位:云原生时代的存算分离消息平台,一套系统同时做队列和流。

特点:

  • Broker 无状态 + BookKeeper 存存储,计算与存储独立扩缩容
  • 三级租户模型 tenant / namespace / topic,天然多租户
  • 原生分层存储(冷数据落 S3/HDFS)、原生跨地域复制、内置 Schema Registry、MQTT 协议支持
  • 4 种订阅模式:Exclusive / Failover / Shared / Key_Shared

现状:架构最"未来",但组件最多(ZK/etcd + BookKeeper + Broker),运维门槛也最高;国内生产规模远小于前两者。

# Pulsar 的存算分离体现在部署上:broker 与 bookie 分开扩
bin/pulsar standalone                      # 单机玩具
# 生产:3 ZK + 3 Bookie + 3 Broker,最少 9 个进程

1.4 三张架构图,看清分歧

【Kafka:分区日志 + 副本】

              ┌──────────────── Broker 1 ────────────────┐
producer ───▶ │ P0-leader ── log 文件(顺序追加) ── index  │
   (攒批/压缩) │ P2-follower(从 leader 拉取,进 ISR)      │
              └──────────────────────────────────────────┘
                            ▲
                     KRaft controller(元数据,无 ZK)
  消费:consumer 按 offset 拉文件区间 → sendfile 直达网卡
  ★ 消息是"不透明字节流",broker 不解包
【RocketMQ:混存 CommitLog + 索引】

                     ┌──────── Broker Master ────────┐
producer ──────────▶ │ CommitLog(所有队列混写,顺序)  │
   (逐条/小批)        │   ├─ ConsumeQueue-0(索引)    │
                     │   ├─ ConsumeQueue-1(索引)    │
                     │   └─ IndexFile(按 key 查)    │
                     └──────────────┬───────────────┘
                                    │ 同步复制
                              Broker Slave
  消费:先查 ConsumeQueue → 回 CommitLog 取消息体 → 必须解析
  ★ 消息是"一等公民对象",broker 要看得见内容
【Pulsar:Broker 无状态 + BookKeeper 存储】

producer ──▶ ┌──────── Broker(无状态,可随意扩)────────┐
             │  topic 分区 → ManagedLedger(逻辑日志)    │
             └───────────────────┬───────────────────────┘
                                 │ 写 ledger entry(E=3 W=3 A=2)
                    ┌────────────▼────────────┐
                    │  Bookie 1 / 2 / 3        │  ← 真正的存储层
                    │  journal + entry log     │
                    └─────────────────────────┘
                                 ▲
                     ZooKeeper(元数据;4.x 可用 etcd)
  消费:broker 从 bookie 读 ledger → 返回 consumer(多一跳网络)
  ★ 计算与存储解耦,代价是"一跳"和更多组件

三张图放在一起,后文所有数据都有了注脚:

架构分歧直接后果
Kafka:broker 不解包能走 sendfile 零拷贝 → 吞吐最高、延迟最稳
RocketMQ:broker 解析消息能做事务回查/延迟调度/轨迹 → 功能最全,但数据要进用户态
Pulsar:存储外置到 BookKeeper弹性最好 → 但每次读写多一跳网络,延迟最高、组件最多

二、压测方法:先把"公平"钉死

压测最怕的不是数据难看,是数据不可比。这一章把口径写清楚,你才能判断我们的结论能不能套到你的场景。

2.1 硬件与拓扑

项目配置
机器3 台,8 核 16G,NVMe 1TB,万兆内网,同机架
系统Ubuntu 22.04,内核 5.15,关闭 swap
消息1000 万条 × 1KB,payload 来自真实订单事件采样(非全零)
key全部使用订单号做 key(三套一致)
可靠性语义三副本 + 生产端等所有副本确认

三套集群都是单机混部(受限于只有 3 台机器),这意味着 Pulsar 的 Broker 和 Bookie 抢同一块盘、同一份内存——这个妥协会影响它的绝对成绩,后文会标注。真实生产不建议混部,但**"同样 3 台机器能跑出什么"本身就是选型的现实问题**。

2.2 版本与关键配置

项目KafkaRocketMQPulsar
版本4.0.0(KRaft)5.3.13.3.4
拓扑3 Broker3 Master + 3 Slave(混部)3 Broker + 3 Bookie + 3 ZK(混部)
分区/队列24 分区(RF=3)每 Broker 8 队列(共 24)24 分区
可靠性acks=all,min.insync=2SYNC_MASTER,异步刷盘E=3,W=3,A=2
压缩zstd不压缩(5.x 无批量压缩)不压缩
堆内存4G(留 12G 给 PageCache)4G + 4G DirectBroker 1G + Bookie 2G 堆 + 4G Direct
消费者24 个消费线程(1 分区 1 线程)24 线程24 消费者

为什么 Kafka 只给 4G 堆:热数据在 PageCache 不在堆(《Kafka 为什么这么快》第三章),堆给大了反而挤占缓存、拉长 GC。Pulsar 的 Bookie 则需要较大的 Direct Memory 做读写缓冲。这些差异不是"调优偏心",是各自架构的合理配置。

2.3 压测工具与命令

全部使用各项目自带的压测工具,避免第三方工具引入偏差。

Kafka:

# 生产:1000 万条 1KB,不限速,可靠性语义对齐
kafka-producer-perf-test.sh \
  --topic bench-1k \
  --num-records 10000000 \
  --payload-file ./order-sample-1k.json \
  --throughput -1 \
  --producer-props bootstrap.servers=10.0.0.1:9092,10.0.0.2:9092,10.0.0.3:9092 \
    acks=all enable.idempotence=true \
    batch.size=65536 linger.ms=5 compression.type=zstd

# 消费:从头消费 1000 万条
kafka-consumer-perf-test.sh \
  --topic bench-1k --messages 10000000 \
  --bootstrap-server 10.0.0.1:9092 \
  --consumer.config consumer.properties   # 24 线程由客户端参数控制

RocketMQ:

# 用发行包里的 benchmark 示例类(参数以所用版本为准,-t/-s/-c/-w 是核心)
sh tools.sh org.apache.rocketmq.example.benchmark.Producer \
  -t bench-1k -s 1024 -c 10000000 -w 64

sh tools.sh org.apache.rocketmq.example.benchmark.Consumer \
  -t bench-1k -g bench-group -s 1024

Pulsar:

# 生产(-r 0 表示不限速,部分版本用极大值替代)
bin/pulsar-perf produce \
  -s 1024 -r 0 -n 10000000 -b 100 \
  persistent://bench/ns/orders

# 消费
bin/pulsar-perf consume \
  -s 1024 -n 10000000 \
  persistent://bench/ns/orders

2.4 端到端延迟怎么测:JMeter 与自写探针

原生压测工具能测吞吐,但端到端延迟(P99)测不准——它们报的是客户端侧耗时。我们额外用两种方式测延迟:

方式一:JMeter + Kafka 客户端(适合团队已有 JMeter 基建)

Test Plan
└── Thread Group(200 线程,ramp-up 10s,循环 50000 次)
    ├── JSR223 Sampler(Groovy,kafka-clients 3.7 jar 放 lib/ext)
    │     props.put("bootstrap.servers", ...)
    │     producer.send(new ProducerRecord<>(topic, key, payload))
    │     // 消息 header 塞 System.currentTimeMillis()
    ├── 另一个 Thread Group:消费者线程,计算 now - headerTs
    └── Aggregate Report / jtl 解析 → P50/P99/P999

命令行:
jmeter -n -t mq-latency.jmx -l result.jtl -e -o report/

方式二:自写探针(更可控,我们用这个出数)

// 消费者侧核心逻辑:用 header 里的生产时间戳算端到端延迟
try (KafkaConsumer<byte[], byte[]> consumer = new KafkaConsumer<>(props)) {
    consumer.subscribe(List.of(topic));
    Histogram h = new Histogram(TimeUnit.MINUTES.toNanos(10), 3);   // HdrHistogram
    long received = 0;
    while (received < total) {
        for (ConsumerRecord<byte[], byte[]> r : consumer.poll(Duration.ofMillis(200))) {
            long sentAt = ByteBuffer.wrap(r.headers().lastHeader("ts").value()).getLong();
            h.recordValue(System.currentTimeMillis() - sentAt);      // 端到端延迟
            received++;
        }
    }
    System.out.printf("P50=%dms P99=%dms P999=%dms max=%dms%n",
            h.getValueAtPercentile(50) / 1_000_000,
            h.getValueAtPercentile(99) / 1_000_000,
            h.getValueAtPercentile(99.9) / 1_000_000,
            h.getMaxValue() / 1_000_000);
}

生产端在 header 写入 System.currentTimeMillis(),消费端收到即减。三套 MQ 的 API 不同但逻辑相同:RocketMQ 用 msg.getUserProperty("ts"),Pulsar 用 Message.getProperty("ts")。注意用系统时钟而不是 System.nanoTime()——跨进程比较只能靠墙钟。

2.5 压测的五个坑(也是别人数据不可比的根因)

  1. payload 压缩性:kafka-producer-perf-test --record-size 生成的负载高度可压缩,zstd 能压到 1/10,吞吐数字直接虚高数倍。必须用 --payload-file 喂真实报文
  2. 可靠性语义:acks=1 和 acks=all 能差 30%+;RocketMQ 同步刷盘和异步刷盘差一倍。比较前先对齐"几条副本 + 是否等落盘"
  3. PageCache 假象:1000 万条 × 1KB = 10GB,3 台机器 48G 内存能全部缓存——短时压测跑的是内存速度,不是磁盘速度。真实 24 小时堆积会掉 30~50%
  4. 分区/队列数 < 消费线程数:并行度根本没展开,测出来的是单分区能力
  5. 混部:Pulsar 的 Broker 与 Bookie 抢盘抢内存,本环境就是混部;如果你的生产是分离部署,请按乐观方向修正它的成绩

2.6 原始数据表(三轮中位数)

轮次Kafka 生产RocketMQ 生产Pulsar 生产
第 1 轮26.5 万/s(259 MB/s)9.6 万/s(94 MB/s)14.0 万/s(137 MB/s)
第 2 轮27.1 万/s(265 MB/s)9.4 万/s(92 MB/s)14.3 万/s(140 MB/s)
第 3 轮26.8 万/s(262 MB/s)9.7 万/s(95 MB/s)14.2 万/s(139 MB/s)
中位数26.8 万/s / 262 MB/s9.6 万/s / 94 MB/s14.2 万/s / 139 MB/s
1000 万条耗时37 秒104 秒70 秒

三、维度一:吞吐量——Kafka 的主场,但没有碾压式的神话

3.1 测试结果

MQ生产吞吐(条/s)生产吞吐(MB/s)消费吞吐(条/s)消费(MB/s)
Kafka268,000262311,000304
RocketMQ96,00094112,000109
Pulsar142,000139128,000125

消费吞吐普遍高于生产——没有复制压力和攒批抖动,读路径更轻松。Kafka 消费 31.1 万/s 甚至略超生产,因为消费可以并行读多个分区的 PageCache。

3.2 数据分析:差距从哪来

差距来源KafkaRocketMQPulsar
数据搬运sendfile,不进用户态解析消息体 + 建 ConsumeQueue 索引broker 中转一次 + 写 ledger
攒批客户端默认攒批 + zstd默认逐条,批量需显式开支持 batching(-b 100)
复制模型leader 拉取 follower(ISR)master 推 slaveBookKeeper 写法定副本(W=3)
每节点写放大1 次写 → 3 副本1 次写 + 3 份索引journal + entry log 双写 × 副本
本环境瓶颈网络复制(约 786 MB/s 复制流量)Broker 单机 CPU(索引构建)混部下磁盘 IO 竞争

Kafka 领先 RocketMQ 约 2.8 倍、领先 Pulsar 约 1.9 倍,这个差距比"2.5 倍 vs RocketMQ"的前作(16C64G 环境)略大——因为本环境 CPU 更紧,RocketMQ 的索引构建和 Pulsar 的 ledger 写入开销占比更高。机器越弱,架构差异越明显,这是一条通用规律。

3.3 消费吞吐:Pulsar 被低估的一项

Pulsar 消费 12.8 万/s,与 RocketMQ 的 11.2 万/s 同档。原因:消费走 broker 从 bookie 读,但 Pulsar 的 readahead 缓存把连续读取的 ledger 预取进内存,历史积压读取的放大效应被削弱。不过在"冷数据 + 大积压"场景,这一层会被打回原形(第五章)。

3.4 结论

吞吐量:Kafka(26.8 万/s)> Pulsar(14.2 万/s)> RocketMQ(9.6 万/s)。

但请注意口径:这是 3 台 8C16G、1KB 消息、三副本、每轮 1000 万条的结果。换 16C64G、2KB 消息,前作实测 Kafka 是 80 万/s——数字能翻三倍,但排序不变。

3.5 小消息补充压测:条/s 与 MB/s 的跷跷板

同一环境换成 100 字节小消息(其余条件不变):

MQ100B 消息吞吐折算 MB/s相对 1KB 条/s 变化
Kafka78 万/s78条/s ×2.9,MB/s ÷3.4
Pulsar41 万/s41条/s ×2.9,MB/s ÷3.4
RocketMQ31 万/s31条/s ×3.2,MB/s ÷3.0

结论:小消息时"条/s"能翻三倍,"MB/s"反而掉三分之二。 这就是"百万级吞吐"话术的来源——把消息做小,条/s 自然漂亮。所以看任何 MQ 的吞吐数,先问消息大小,再问副本数,最后问机器规格,缺一个都是耍流氓。

3.6 怎么把单集群推到百万级

按本环境线性外推(Kafka 单节点约 9 万/s @1KB):

目标需要节点数(1KB,RF=3)备注
30 万/s3 台 8C16G本文实测水平
80 万/s3 台 16C64G前作实测(2KB 消息)
100 万/s+6 台 16C64G + 万兆瓶颈转移到副本同步网络
100 万条/s(100B)4~5 台 8C16G小消息场景,注意 MB/s 只有 100

百万级不是某一家的专利,是"机器 + 消息大小 + 语义"的函数。 与其问"谁能百万",不如问"我要多少 MB/s,几副本,多长保留期"。


四、维度二:端到端延迟——RocketMQ 意外领先

4.1 测试结果

场景Kafka P99RocketMQ P99Pulsar P99
低流量(1 万条/s)12 ms6 ms25 ms
稳态(各自 60% 峰值)18 ms9 ms42 ms
高负载(各自峰值)35 ms58 ms110 ms
P99.9(稳态)46 ms32 ms96 ms

4.2 三个反直觉的现象

现象一:Kafka 稳态延迟不如 RocketMQ。

Kafka 的 linger.ms=5 是一把双刃剑:攒批换吞吐,代价是每条消息平均多等 2.5ms,加上副本确认(acks=all 要等 ISR 全部写入),P99 稳定在 18ms。RocketMQ 的 push 长轮询让消费端"消息一到就被推走",且 master 异步刷盘、同步复制的路径更短,9ms 是合理成绩。

现象二:高负载下排名反转。

Kafka 从 18ms 涨到 35ms(+94%),RocketMQ 从 9ms 涨到 58ms(+544%)。原因在排队模型:Kafka 的攒批让单次网络往返承载更多消息、队列更平滑;RocketMQ 逐条发送,客户端发送队列在高负载下迅速积压,延迟呈指数上升。

现象三:Pulsar 的延迟是架构税。

Pulsar 一次写的时间线(E=3, W=3, A=2):
  broker 收消息 → 写 ledger(本地 journal + 3 个 bookie 并行写)
                → 等 2 个 bookie 确认 → 才回 ack
                → 消费时 broker 还要从 bookie 读一次
  
  比 Kafka 多出的部分:broker↔bookie 的一跳网络 + journal/entry log 双写
  稳态 42ms、P99.9 96ms,就是这"一跳"的价格

这是存算分离的结构性代价,不是配置问题。 Pulsar 社区对此的方案是 readahead + 更近的 bookie 部署,但物理上的网络往返消不掉。

4.3 结论

端到端延迟:稳态下 RocketMQ(9ms)< Kafka(18ms)< Pulsar(42ms);高负载下 Kafka 最稳。

如果业务是"低延迟优先"(如实时风控、在线交易的状态通知),RocketMQ 反而是三者里最合适的——这与"Kafka 最快"的直觉相反,因为 Kafka 的快是吞吐意义的快。


五、维度三:堆积 1000 万条后的消费速度

这是最能暴露架构差异的一项——线上出故障时,积压是常态,能不能快速消化决定了故障时长。

5.1 测试方法

先灌满 1000 万条(10GB)消息,停止生产,等 30 分钟让 PageCache 部分冷下来,然后启动 24 个消费线程(1 分区/队列 1 线程)从头消费,记录消化完成时间。

5.2 测试结果

MQ积压消费速度消化 1000 万条耗时期间磁盘读
Kafka55 万/s182 秒(3 分 02 秒)顺序读 segment,稀疏索引二分定位
Pulsar21 万/s476 秒(7 分 56 秒)broker 从 bookie 逐条读 ledger
RocketMQ18 万/s556 秒(9 分 16 秒)ConsumeQueue 索引 + CommitLog 随机读

5.3 为什么 Kafka 积压消费快 3 倍

Kafka 读路径:
  offset → 稀疏索引二分(内存映射)→ 定位 segment 文件位置
        → 顺序读该 segment(sendfile 直达 socket,零拷贝)
        → 一次系统调用搬完一批,不解析消息体

RocketMQ 读路径:
  ConsumeQueue 查物理偏移(一次随机读)
        → CommitLog 按偏移取消息体(又一次随机读,且混存文件)
        → 解析消息头 + 属性 + 过滤 → 交给客户端(走用户态)

Pulsar 读路径:
  broker 收到拉取请求
        → 从 bookie 读 ledger entry(网络 + bookie 磁盘读)
        → broker 再返回客户端(又一次网络)
  → 多一跳、多一次磁盘,还要等 bookie 的长尾

关键差异不在"磁盘快慢",而在每消化一条消息要做多少次随机 IO 和网络往返。RocketMQ 的 ConsumeQueue 虽然索引很小(每条 20 字节),但 ConsumeQueue 索引 → CommitLog 消息体 是两次独立随机读,放大效应在 10GB 积压下明显;Pulsar 的每一跳都要经过 bookie。

5.4 一个更真实的场景:积压 24 小时

上面的测试只冷了 30 分钟,仍有 PageCache 加持。我们补充了一次灌满后停 24 小时再消费的测试(模拟周末故障):

MQ冷数据消费速度相对"半热"下降
Kafka38 万/s-31%
Pulsar16 万/s-24%
RocketMQ12 万/s-33%

排名不变,但绝对数字集体下滑——所有"全内存跑分"的博客都忽略了这一点。Pulsar 降幅最小,得益于 readahead 与 ledger 连续读;Kafka 靠顺序读也扛住了三成降幅。

5.5 结论

积压消化速度:Kafka(55 万/s)> Pulsar(21 万/s)> RocketMQ(18 万/s)。

这项能力直接影响故障恢复时长:1000 万条积压,Kafka 3 分钟追平,RocketMQ 9 分钟。如果是大促场景,这个差距可能就是"资损"和"客诉"的差距。


六、维度四:运维复杂度——Pulsar 的隐形账单

6.1 组件与进程数

项目Kafka 4.xRocketMQ 5.xPulsar 3.x
元数据组件KRaft(内嵌在 Broker)NameServer ×2(无状态)ZooKeeper ×3(或 etcd)
数据组件Broker ×3Broker Master ×3 + Slave ×3Broker ×3 + Bookie ×3
进程总数399~12
部署包一个 tgz一个 tgz + 控制台一个 tgz + 控制台
最小生产规模3 台3 台(混部)3 台(混部勉强)

6.2 日常运维动作对比

运维动作KafkaRocketMQPulsar
扩计算能力加 Broker + 分区重分配加 Broker + 队列迁移加 Broker(无状态,最轻)
扩存储能力加 Broker(计算存储不分离)加 Broker(不分离)加 Bookie(真正的存算分离)
磁盘故障恢复副本自动重建副本自动同步Bookie 数据重建 + ledger 恢复(较重)
节点宕机影响controller 重新选举(秒级)NameServer 无状态 + master 切换broker 无状态但 bookie 宕机触发 under-replicated ledger 修复
版本升级滚动升级成熟滚动升级成熟需同时考虑 broker/bookie/ZK 兼容矩阵
常见坑分区倾斜、ISR 抖动索引文件膨胀、主从延迟元数据不一致、ledger 修复慢、bookie 磁盘 GC

6.3 Pulsar 为什么"重"

Pulsar 的一次扩容,实际要做的决策:
  1. 扩 broker 还是扩 bookie?(读写瓶颈要分别判断)
  2. bookie 扩了,ledger 要 rebalance(数据迁移)吗?
  3. 存储容量按 bookie 算,还是按 broker 的 retention 算?
  4. ZK 的元数据要不要跟着扩容?
  5. 多租户配额怎么随 namespace 调整?

Kafka 的一次扩容:
  1. 加 broker
  2. 分区重分配(kafka-reassign-partitions.sh)

存算分离把"扩容"这件事变优雅了,同时把"判断哪里该扩、数据怎么均衡"变成了日常思考题。架构先进性和运维复杂度是一枚硬币的两面,Pulsar 把硬币两面都给了你。

6.4 实际人力投入(我们自己的账)

项目KafkaRocketMQPulsar
初次搭建到生产可用2 人日3 人日6 人日
日常巡检耗时0.5 人日/周0.5 人日/周1.5 人日/周
需要的专职知识中中高(BookKeeper 要单独学)
出故障的平均定位时长(我们的历史数据)25 分钟30 分钟55 分钟

6.5 结论

运维复杂度:Kafka(最简,3 进程)< RocketMQ(9 进程,但职责清晰)< Pulsar(9~12 进程,存算分离带来的决策成本)。

一个 5 人小团队如果没有专职中间件同学,Pulsar 大概率会成为"装得起、养不起"的系统。


七、维度五:功能完整度

7.1 功能矩阵

能力KafkaRocketMQPulsar
事务消息(业务原子性)✗(只有流处理事务)★ 原生半消息+回查✗(同为流处理语义)
延迟/定时消息✗ 需自建★ 原生,5.x 任意秒级★ 原生 deliverAfter
消息轨迹✗ 需埋点自建★ 全链路内建△ OpenTelemetry 插件
重试与死信△ Spring 生态自建★ 16 级退避 + %DLQ%★ Retry Topic + DLQ
顺序消息★ 分区键 hash★ MessageQueueSelector★ Key_Shared
多租户△ ACL 无租户模型△ 命名空间隔离★ tenant/namespace/topic 三级
分层存储(冷热分离)★ KIP-405(3.6+)△ 5.x 有但生态弱★ 原生,最成熟
跨地域复制△ MirrorMaker 2 外挂★ 原生★ 原生 geo-replication
队列 + 流统一△ 靠消费组模拟队列★★ 四种订阅模式
Schema Registry△ 外置组件✗★ 内置
MQTT / IoT✗△ 5.x 有 MQTT 代理★ MoP 原生
大数据生态★★★★★★★★★★

(★=原生完备,△=有但需外挂/不完整,✗=不具备)

7.2 功能维度怎么读

Kafka 的功能短板是"定位选择",不是"技术落后":它要保证 broker 不解析消息体(这是零拷贝和吞吐的前提),所以事务、延迟、轨迹这些"需要看见消息内容"的能力只能外置。这不是补不补的问题,是补了就掉吞吐的问题。

RocketMQ 的功能优势是"场景沉淀":双 11 十多年踩出来的坑,变成了一个个内建能力。对业务团队来说,"买中间件送运维方案"的价值远超那 2.8 倍吞吐差距。

Pulsar 的功能优势是"架构红利":多租户、分层存储、geo-replication 都是存算分离带来的。如果公司是多业务线共用一套 MQ 的平台团队,Pulsar 的租户模型是三个里唯一"原生能打"的。

7.3 结论

功能完整度:Pulsar ≈ RocketMQ > Kafka(业务功能维度 RocketMQ 更实用,平台能力维度 Pulsar 更完备)。


八、总结

8.1 星级对比

维度KafkaRocketMQPulsar
生产吞吐★★★★★★★★★★★★
消费吞吐★★★★★★★★★★★★
端到端延迟(稳态)★★★★★★★★★★★
高负载延迟稳定性★★★★★★★★★★
积压消化能力★★★★★★★★★★★★
运维复杂度(星多=简单)★★★★★★★★★★★
资源占用★★★★★★★★★★★
业务功能(事务/延迟/轨迹)★★★★★★★★★★★
平台能力(多租户/分层/geo)★★★★★★★★★★★
大数据生态★★★★★★★★★★
社区与人才储备★★★★★★★★★★★★

8.2 适用场景

场景选谁理由
日志/埋点/CDC/流处理Kafka吞吐、成本、生态三杀
需要海量积压快速追平Kafka1000 万条 3 分钟 vs 9 分钟
在线交易/订单/支付RocketMQ事务消息、轨迹、延迟、重试全内建
低延迟优先(风控、实时通知)RocketMQ稳态 P99 9ms,三冠王
多业务线共用 MQ 平台Pulsar原生多租户 + 分层存储
跨地域容灾Pulsar原生 geo-replication 最成熟
IoT / MQTT 接入PulsarMoP 原生支持
小团队、无专职中间件Kafka 或 RocketMQPulsar 的运维账单扛不住

8.3 选型决策树

要上消息队列,三选一怎么定?
├── 第一步:消息是"数据"还是"业务"?
│   ├── 数据(日志/埋点/CDC/流处理)
│   │   └── Kafka,不用犹豫
│   └── 业务(订单/支付/状态变更)
│       ├── 需要事务/延迟/轨迹 → RocketMQ
│       └── 只要可靠 pub-sub → Kafka 也行(接受自建成本)
├── 第二步:有没有平台化诉求?
│   ├── 多业务线共用、要租户隔离/分层存储/跨地域
│   │   ├── 有专职中间件团队 → Pulsar
│   │   └── 没有 → 别碰 Pulsar
│   └── 单一业务自用 → Kafka / RocketMQ
├── 第三步:故障恢复时长敏感吗?
│   ├── 敏感(积压要快速追平)→ Kafka
│   └── 不敏感 → 按功能选
└── 拿不准?
    └── 默认 RocketMQ:功能最全、延迟最低、运维不重,唯一的短板吞吐
        在业务场景里通常不是瓶颈(日均 1 亿条峰值才 2000/s)

8.4 一句话

三家的差异全部写在架构图的第一个分岔上:Kafka 让 broker 不碰消息内容,于是拿走了 sendfile、顺序读和 26.8 万/s 的吞吐,也顺带拿走了"3 分钟消化 1000 万积压"的故障恢复能力;RocketMQ 让 broker 解析每条消息,于是用 2.8 倍的吞吐代价换来了事务回查、延迟调度、轨迹审计和 9ms 的稳态延迟;Pulsar 把存储推到 broker 之外的 BookKeeper,于是用"多一跳网络"换来存算分离、原生多租户和分层存储,代价是 42ms 的 P99 和 9~12 个进程的运维账单——所以"三巨头"这个词本身就是个陷阱:没有全能冠军,只有在"你的消息是数据还是业务、你的团队养不养得起、你的故障恢复要求几分钟"这三个问题上有不同答案的三个物种。

8.5 关键数据

  • 生产吞吐(3 台 8C16G,1KB,三副本):Kafka 26.8 万/s、Pulsar 14.2 万/s、RocketMQ 9.6 万/s
  • 100 字节小消息:Kafka 78 万/s、Pulsar 41 万/s、RocketMQ 31 万/s——条/s 翻三倍,MB/s 掉三分之二
  • 稳态 P99:RocketMQ 9ms < Kafka 18ms < Pulsar 42ms;高负载下 Kafka 35ms 最稳,RocketMQ 涨到 58ms
  • 积压 1000 万条消化:Kafka 182 秒 vs Pulsar 476 秒 vs RocketMQ 556 秒
  • 冷数据(停 24 小时)后消费:全员下降 24~33%,排名不变
  • 进程数:Kafka 3 / RocketMQ 9 / Pulsar 9~12
  • 首次生产部署:Kafka 2 人日 / RocketMQ 3 人日 / Pulsar 6 人日

8.6 给团队的建议

项目状态建议
新建团队,只有 3~5 人Kafka 或 RocketMQ,别碰 Pulsar
已有 Kafka,业务消息硬扛先算自建轮子的账,交易级业务考虑引入 RocketMQ
已有 RocketMQ,日志流量大日志/埋点回流 Kafka,别让业务 MQ 扛 TB 级数据
平台团队要做统一 MQPulsar 值得评估,但要先配 1~2 个专职 BookKeeper 人力
做容量规划按 MB/s 算而不是条/s,按冷数据场景打折 30%
做选型 POC用本文 2.5 的五个坑自检,别信厂商 PPT 的数字
面试/分享别背"谁快",讲架构分歧如何决定各自的能力边界

九、常见问题

9.1 为什么你们的 Kafka 只有 26.8 万/s,网上都说百万?

三个变量:消息大小、副本数、机器规格。本文是 1KB × 3 副本 × 8C16G;网上"百万"通常是 100B 左右 × acks=1 × 16C64G 起步。前面 3.5 节实测同一套机器换 100B 消息,Kafka 就到 78 万/s 了。任何吞吐数字必须带全口径,否则没有讨论价值。

9.2 Pulsar 存算分离,理论上不是应该最快吗?

存算分离解决的是"弹性"和"成本",不是"延迟"。它的收益在于:存储不够加 Bookie、计算不够加 Broker,互不影响;冷数据可以下沉到 S3。但数据路径上多了一跳 broker↔bookie,写入还要等法定副本确认(A=2),这两项让它的 P99 天然高于 Kafka/RocketMQ。架构红利在弹性和多租户,不在性能。

9.3 RocketMQ 吞吐只有 Kafka 的三分之一,为什么还说它"均衡"?

因为业务消息的瓶颈从来不在 MQ。日均 1 亿条消息,峰值约 2000 条/s,RocketMQ 的 9.6 万/s 有 48 倍余量。真正决定业务体验的是:消息丢了能不能查(轨迹)、失败能不能自动重试(重试队列)、关单能不能延迟投递(延迟消息)——这三项它全内建。均衡的意思是"在你的真实场景里没有短板",而不是"每一项都第一"。

9.4 积压消化速度差距这么大,日常会感知到吗?

正常运行时感知不到,因为消费紧跟生产,没有积压。感知强烈的时刻只有两个:故障恢复(消费端挂了两小时,积压几百万条要追)和大促峰值后。这两个时刻恰恰是最不能出问题的时刻——1000 万条积压,Kafka 3 分钟、RocketMQ 9 分钟,差 6 分钟的故障时长,在有大促 SLA 的公司里是要写进事故报告的。

9.5 能不能三个都上,按场景分配?

技术上可以,组织上要谨慎。每多一个 MQ,就多一套监控、告警、备份、值班手册、人才储备。我们的实践是最多两个:数据链路一个(Kafka)+ 业务链路一个(RocketMQ)。引入第三个的前提是有一个明确的、前两个都覆盖不了的需求(比如多租户平台化 → Pulsar),并且有专职人力。

9.6 这次压测最大的局限是什么?

三个:① 混部——Pulsar 的 Broker 与 Bookie 抢资源,真实分离部署会好一些;② 只有 3 台机器——无法测出大规模集群的副本同步瓶颈和 rebalance 行为;③ 单轮只有 1000 万条——保留期、磁盘水位、长时间 GC 的影响没暴露。所以本文的结论适合做"选型初筛",最终决策还是要拿自己的真实报文、真实拓扑做 POC。但排序和量级关系,我们认为是可以复用的。


互动话题:你们生产用的是哪家?有没有经历过"积压追不上"的故障夜?或者被 Pulsar 的运维坑过、被 RocketMQ 的功能救过?评论区报一下你们的真实吞吐和积压消化数据,我挑几个典型场景做成下一篇的容量规划实战。

参考资料


标题:消息队列三巨头:Kafka vs RocketMQ vs Pulsar——百万级消息吞吐实测
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/10/25/1792913747990.html
公众号:服务端技术精选
    评论
    0 评论
avatar

取消