消息队列三巨头: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 万/s | RocketMQ 9.6 万/s | Kafka 领先 2.8 倍 |
| 端到端 P99(稳态) | RocketMQ 9ms | Kafka 18ms | Pulsar 42ms | push 长轮询占优 |
| 积压 1000 万条的消化速度 | Kafka 55 万/s(182 秒) | Pulsar 21 万/s | RocketMQ 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 版本与关键配置
| 项目 | Kafka | RocketMQ | Pulsar |
|---|---|---|---|
| 版本 | 4.0.0(KRaft) | 5.3.1 | 3.3.4 |
| 拓扑 | 3 Broker | 3 Master + 3 Slave(混部) | 3 Broker + 3 Bookie + 3 ZK(混部) |
| 分区/队列 | 24 分区(RF=3) | 每 Broker 8 队列(共 24) | 24 分区 |
| 可靠性 | acks=all,min.insync=2 | SYNC_MASTER,异步刷盘 | E=3,W=3,A=2 |
| 压缩 | zstd | 不压缩(5.x 无批量压缩) | 不压缩 |
| 堆内存 | 4G(留 12G 给 PageCache) | 4G + 4G Direct | Broker 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 压测的五个坑(也是别人数据不可比的根因)
- payload 压缩性:
kafka-producer-perf-test --record-size生成的负载高度可压缩,zstd 能压到 1/10,吞吐数字直接虚高数倍。必须用--payload-file喂真实报文 - 可靠性语义:acks=1 和 acks=all 能差 30%+;RocketMQ 同步刷盘和异步刷盘差一倍。比较前先对齐"几条副本 + 是否等落盘"
- PageCache 假象:1000 万条 × 1KB = 10GB,3 台机器 48G 内存能全部缓存——短时压测跑的是内存速度,不是磁盘速度。真实 24 小时堆积会掉 30~50%
- 分区/队列数 < 消费线程数:并行度根本没展开,测出来的是单分区能力
- 混部: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/s | 9.6 万/s / 94 MB/s | 14.2 万/s / 139 MB/s |
| 1000 万条耗时 | 37 秒 | 104 秒 | 70 秒 |
三、维度一:吞吐量——Kafka 的主场,但没有碾压式的神话
3.1 测试结果
| MQ | 生产吞吐(条/s) | 生产吞吐(MB/s) | 消费吞吐(条/s) | 消费(MB/s) |
|---|---|---|---|---|
| Kafka | 268,000 | 262 | 311,000 | 304 |
| RocketMQ | 96,000 | 94 | 112,000 | 109 |
| Pulsar | 142,000 | 139 | 128,000 | 125 |
消费吞吐普遍高于生产——没有复制压力和攒批抖动,读路径更轻松。Kafka 消费 31.1 万/s 甚至略超生产,因为消费可以并行读多个分区的 PageCache。
3.2 数据分析:差距从哪来
| 差距来源 | Kafka | RocketMQ | Pulsar |
|---|---|---|---|
| 数据搬运 | sendfile,不进用户态 | 解析消息体 + 建 ConsumeQueue 索引 | broker 中转一次 + 写 ledger |
| 攒批 | 客户端默认攒批 + zstd | 默认逐条,批量需显式开 | 支持 batching(-b 100) |
| 复制模型 | leader 拉取 follower(ISR) | master 推 slave | BookKeeper 写法定副本(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 字节小消息(其余条件不变):
| MQ | 100B 消息吞吐 | 折算 MB/s | 相对 1KB 条/s 变化 |
|---|---|---|---|
| Kafka | 78 万/s | 78 | 条/s ×2.9,MB/s ÷3.4 |
| Pulsar | 41 万/s | 41 | 条/s ×2.9,MB/s ÷3.4 |
| RocketMQ | 31 万/s | 31 | 条/s ×3.2,MB/s ÷3.0 |
结论:小消息时"条/s"能翻三倍,"MB/s"反而掉三分之二。 这就是"百万级吞吐"话术的来源——把消息做小,条/s 自然漂亮。所以看任何 MQ 的吞吐数,先问消息大小,再问副本数,最后问机器规格,缺一个都是耍流氓。
3.6 怎么把单集群推到百万级
按本环境线性外推(Kafka 单节点约 9 万/s @1KB):
| 目标 | 需要节点数(1KB,RF=3) | 备注 |
|---|---|---|
| 30 万/s | 3 台 8C16G | 本文实测水平 |
| 80 万/s | 3 台 16C64G | 前作实测(2KB 消息) |
| 100 万/s+ | 6 台 16C64G + 万兆 | 瓶颈转移到副本同步网络 |
| 100 万条/s(100B) | 4~5 台 8C16G | 小消息场景,注意 MB/s 只有 100 |
百万级不是某一家的专利,是"机器 + 消息大小 + 语义"的函数。 与其问"谁能百万",不如问"我要多少 MB/s,几副本,多长保留期"。
四、维度二:端到端延迟——RocketMQ 意外领先
4.1 测试结果
| 场景 | Kafka P99 | RocketMQ P99 | Pulsar P99 |
|---|---|---|---|
| 低流量(1 万条/s) | 12 ms | 6 ms | 25 ms |
| 稳态(各自 60% 峰值) | 18 ms | 9 ms | 42 ms |
| 高负载(各自峰值) | 35 ms | 58 ms | 110 ms |
| P99.9(稳态) | 46 ms | 32 ms | 96 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 万条耗时 | 期间磁盘读 |
|---|---|---|---|
| Kafka | 55 万/s | 182 秒(3 分 02 秒) | 顺序读 segment,稀疏索引二分定位 |
| Pulsar | 21 万/s | 476 秒(7 分 56 秒) | broker 从 bookie 逐条读 ledger |
| RocketMQ | 18 万/s | 556 秒(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 | 冷数据消费速度 | 相对"半热"下降 |
|---|---|---|
| Kafka | 38 万/s | -31% |
| Pulsar | 16 万/s | -24% |
| RocketMQ | 12 万/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.x | RocketMQ 5.x | Pulsar 3.x |
|---|---|---|---|
| 元数据组件 | KRaft(内嵌在 Broker) | NameServer ×2(无状态) | ZooKeeper ×3(或 etcd) |
| 数据组件 | Broker ×3 | Broker Master ×3 + Slave ×3 | Broker ×3 + Bookie ×3 |
| 进程总数 | 3 | 9 | 9~12 |
| 部署包 | 一个 tgz | 一个 tgz + 控制台 | 一个 tgz + 控制台 |
| 最小生产规模 | 3 台 | 3 台(混部) | 3 台(混部勉强) |
6.2 日常运维动作对比
| 运维动作 | Kafka | RocketMQ | Pulsar |
|---|---|---|---|
| 扩计算能力 | 加 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 实际人力投入(我们自己的账)
| 项目 | Kafka | RocketMQ | Pulsar |
|---|---|---|---|
| 初次搭建到生产可用 | 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 功能矩阵
| 能力 | Kafka | RocketMQ | Pulsar |
|---|---|---|---|
| 事务消息(业务原子性) | ✗(只有流处理事务) | ★ 原生半消息+回查 | ✗(同为流处理语义) |
| 延迟/定时消息 | ✗ 需自建 | ★ 原生,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 星级对比
| 维度 | Kafka | RocketMQ | Pulsar |
|---|---|---|---|
| 生产吞吐 | ★★★★★ | ★★★ | ★★★★ |
| 消费吞吐 | ★★★★★ | ★★★ | ★★★★ |
| 端到端延迟(稳态) | ★★★★ | ★★★★★ | ★★ |
| 高负载延迟稳定性 | ★★★★★ | ★★★ | ★★ |
| 积压消化能力 | ★★★★★ | ★★★ | ★★★★ |
| 运维复杂度(星多=简单) | ★★★★★ | ★★★★ | ★★ |
| 资源占用 | ★★★★★ | ★★★ | ★★★ |
| 业务功能(事务/延迟/轨迹) | ★★ | ★★★★★ | ★★★★ |
| 平台能力(多租户/分层/geo) | ★★★ | ★★★ | ★★★★★ |
| 大数据生态 | ★★★★★ | ★★ | ★★★ |
| 社区与人才储备 | ★★★★★ | ★★★★ | ★★★ |
8.2 适用场景
| 场景 | 选谁 | 理由 |
|---|---|---|
| 日志/埋点/CDC/流处理 | Kafka | 吞吐、成本、生态三杀 |
| 需要海量积压快速追平 | Kafka | 1000 万条 3 分钟 vs 9 分钟 |
| 在线交易/订单/支付 | RocketMQ | 事务消息、轨迹、延迟、重试全内建 |
| 低延迟优先(风控、实时通知) | RocketMQ | 稳态 P99 9ms,三冠王 |
| 多业务线共用 MQ 平台 | Pulsar | 原生多租户 + 分层存储 |
| 跨地域容灾 | Pulsar | 原生 geo-replication 最成熟 |
| IoT / MQTT 接入 | Pulsar | MoP 原生支持 |
| 小团队、无专职中间件 | Kafka 或 RocketMQ | Pulsar 的运维账单扛不住 |
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 级数据 |
| 平台团队要做统一 MQ | Pulsar 值得评估,但要先配 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 官方文档(设计与性能):https://kafka.apache.org/documentation/#design
- RocketMQ 官方文档(架构/事务/延迟/重试):https://rocketmq.apache.org/docs/
- Pulsar 官方文档(架构/BookKeeper/多租户):https://pulsar.apache.org/docs/
- Pulsar BookKeeper 副本模型(E/W/A):https://bookkeeper.apache.org/docs/replication/
- Kafka KIP-405 分层存储:https://cwiki.apache.org/confluence/display/KAFKA/KIP-405
- 本系列前作:《Kafka 为什么这么快?——顺序写+页缓存+零拷贝+批量,四板斧拆解》
- 本系列前作:《Kafka 消息不丢失实战:生产者ack+副本ISR+消费者手动提交的完整配置》
- 本系列前作:《Kafka 顺序消息实战:同一个订单的消息必须按序消费——分区键设计+单分区单消费者》
- 本系列前作:《面试官:Kafka 为什么比 RocketMQ 快?——不是快,是场景不同》
- 本系列前作:《Kafka 运维必备工具清单:这5个工具让排障快10倍》
标题:消息队列三巨头:Kafka vs RocketMQ vs Pulsar——百万级消息吞吐实测
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/10/25/1792913747990.html
公众号:服务端技术精选
- 引言
- 一、三巨头简介
- 1.1 Kafka
- 1.2 RocketMQ
- 1.3 Pulsar
- 1.4 三张架构图,看清分歧
- 二、压测方法:先把"公平"钉死
- 2.1 硬件与拓扑
- 2.2 版本与关键配置
- 2.3 压测工具与命令
- 2.4 端到端延迟怎么测:JMeter 与自写探针
- 2.5 压测的五个坑(也是别人数据不可比的根因)
- 2.6 原始数据表(三轮中位数)
- 三、维度一:吞吐量——Kafka 的主场,但没有碾压式的神话
- 3.1 测试结果
- 3.2 数据分析:差距从哪来
- 3.3 消费吞吐:Pulsar 被低估的一项
- 3.4 结论
- 3.5 小消息补充压测:条/s 与 MB/s 的跷跷板
- 3.6 怎么把单集群推到百万级
- 四、维度二:端到端延迟——RocketMQ 意外领先
- 4.1 测试结果
- 4.2 三个反直觉的现象
- 4.3 结论
- 五、维度三:堆积 1000 万条后的消费速度
- 5.1 测试方法
- 5.2 测试结果
- 5.3 为什么 Kafka 积压消费快 3 倍
- 5.4 一个更真实的场景:积压 24 小时
- 5.5 结论
- 六、维度四:运维复杂度——Pulsar 的隐形账单
- 6.1 组件与进程数
- 6.2 日常运维动作对比
- 6.3 Pulsar 为什么"重"
- 6.4 实际人力投入(我们自己的账)
- 6.5 结论
- 七、维度五:功能完整度
- 7.1 功能矩阵
- 7.2 功能维度怎么读
- 7.3 结论
- 八、总结
- 8.1 星级对比
- 8.2 适用场景
- 8.3 选型决策树
- 8.4 一句话
- 8.5 关键数据
- 8.6 给团队的建议
- 九、常见问题
- 9.1 为什么你们的 Kafka 只有 26.8 万/s,网上都说百万?
- 9.2 Pulsar 存算分离,理论上不是应该最快吗?
- 9.3 RocketMQ 吞吐只有 Kafka 的三分之一,为什么还说它"均衡"?
- 9.4 积压消化速度差距这么大,日常会感知到吗?
- 9.5 能不能三个都上,按场景分配?
- 9.6 这次压测最大的局限是什么?
- 参考资料
评论