上一篇解决了生产运维中的集群扩缩与故障排查。这一篇进入对比视角。

两套系统面对同一类问题——高吞吐消息传递——各自作出了一组互斥的选择,这些选择决定了各自擅长什么、在什么场景下会出现明显短板。

架构差异速览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Kafka                              RocketMQ
───────────────────────── ──────────────────────────────
存储: partition 文件 存储: CommitLog (单文件) +
每个 partition 独立段 ConsumeQueue (消费索引)

控制: ZooKeeper (旧) 控制: NameServer (轻量)
KRaft (自 Kafka 2.8 起)

消费: consumer group + partition 消费: consumer group + queue
静态/动态分配 Pop 模式 (RocketMQ 5.x)
仅 pull pull / push / pop 可选

事务: 两阶段 + coordinator 事务: 半消息 + 回查机制

延迟: 无原生支持 延迟: 18 档固定级别 (4.x)
任意精度 (5.x TimerWheel)

两种架构都能支撑每秒百万级消息吞吐,差异不在量级,在于到达那个量级所依赖的不同约束。

存储模型对比

Kafka 每个 partition 对应磁盘上的一个独立目录,目录下是顺序追加的 log segment 文件(默认 .log + .index + .timeindex 三件套,见 kafka-logs/<topic>-<partition>/)。Broker 写消息时,只需向当前活跃 segment 顺序追加,读消息时借助 .index 定位 offset 对应的物理偏移量,然后用 sendfile 零拷贝传输。这个设计对单 topic 场景极为高效——OS page cache 命中率高,磁盘 I/O 是纯顺序的。

问题出现在 partition 数量增大时。每个 partition 是独立文件,10 个 topic 每个 100 个 partition 就是 1000 个目录,每个目录有多个 segment,随机 I/O 压力在 partition 数量超过一定门槛后会非线性增长。Kafka 官方文档和社区基准均指出,单 Broker 的 partition 数上限大约在几千个,超过这个量级写延迟会显著上升。

RocketMQ 的存法不同。Broker 把所有 topic 的消息追加到同一个 CommitLog 文件(默认 1 GB 为一个文件,路径 ~/store/commitlog/),额外维护 ConsumeQueue——每个 topic 的每个 queue 对应一个定长的 ConsumeQueue 文件,里面存的是消息在 CommitLog 里的物理偏移量、消息大小和 tag hashcode,三元组各 8/4/8 字节,共 20 字节。

消费方读消息分两步:先从 ConsumeQueue 读索引项拿到 CommitLog 偏移量,再去 CommitLog 读实际消息体。这多一次读,但 ConsumeQueue 是定长顺序文件,page cache 命中率极高,实测额外开销很小。

这套设计的关键优势:不管集群有多少 topic,写入路径只有一个顺序写目标。从 1000 个 topic 增加到 10000 个 topic,写吞吐基本不受影响,因为 CommitLog 的追加模式没有变化。代价是读放大(两步读取)和索引同步的复杂度。

消费模型对比

Kafka 的消费单元是 consumer group + partition 的分配关系。每个 partition 在任意时刻只有一个 consumer 实例消费,partition 数决定了最大消费并发度,多余的 consumer 实例会闲置。消费进度以 offset 形式记录在 __consumer_offsets topic,提交方式有自动和手动两种。

这个模型的上限是 partition 数,也是其伸缩瓶颈所在:consumer 实例数超过 partition 数就有浪费,追加并发度只能靠增加 partition,而 partition 数受限于 Broker 文件描述符和磁盘 I/O 能力。

RocketMQ 的消费单元是 consumer group + message queue(queue 与 Kafka partition 概念类似,但底层存储路径不同)。4.x 的传统 push/pull 模式下消费并发度同样受 queue 数量限制。

RocketMQ 5.x 引入了 Pop 消费模式。Consumer 不再绑定到特定 queue,而是向 Broker 发起 Pop 请求,Broker 对消息加锁后返回,Consumer 处理完再 ACK,未 ACK 的消息在超时后由 Broker 重新投递。Pop 模式打破了 queue 数和 consumer 实例数之间的绑定关系,任意数量的 consumer 实例都可以从任意 queue 拉取,有效提升了消费的弹性——这对 serverless 场景(consumer 实例数随负载动态伸缩)尤其有价值。

广播模式(broadcast)是两个系统都支持的消费语义:每个 consumer 实例独立接收所有消息。Kafka 通过让每个实例使用独立 consumer group 实现,RocketMQ 有 BROADCASTING 枚举直接控制。

事务与顺序消息

Kafka 的事务由 transactional producer 实现,核心是跨 partition 原子写入:producer 发送多条消息到不同 partition,要么全部对 consumer 可见,要么全部不可见。事务状态记录在 __transaction_state topic,由 transaction coordinator(位于某个 Broker)管理两阶段提交。这套机制常用于"读-处理-写"的流处理场景(Kafka Streams 的 EOS 保障即依赖于此),但它不是"业务事务"语义——不能与数据库事务绑定。

RocketMQ 的事务消息用半消息(half message)机制实现。Producer 先发一条半消息到 Broker(此时对 consumer 不可见),完成本地事务后发送 Commit 或 Rollback。如果 Broker 长时间未收到确认,会主动回查 Producer 端的事务状态接口(TransactionListener.checkLocalTransaction)。这套机制更接近业务分布式事务的需求——例如"先写数据库,再发消息"这类场景——但依赖 Producer 实现回查逻辑,且最终一致性而非强一致性。

顺序消息方面,两个系统都只保证 partition/queue 级别的顺序——同一 partition 或 queue 内的消息按写入顺序投递。全局顺序(所有消息严格有序)需要退化为单 partition/queue,吞吐会大幅下降。RocketMQ 的文档中将顺序消息分为"全局顺序"和"分区顺序"两类,全局顺序场景下推荐 topic 只开一个 queue;Kafka 同样如此,全局顺序要求单 partition。

延迟消息

Kafka 在 3.x 版本之前没有原生延迟消息支持。常见的实现方案是建立若干个延迟 topic(如 delay-5sdelay-1mdelay-10m),producer 把消息发到对应延迟 topic,另起一个 consumer 检查时间戳,到期后再转发到目标 topic。这套方案实现成本较高、精度受限于检查频率,也不在 Kafka 官方发行版内。截至 Kafka 3.7,KIP-848 已合并但延迟队列功能(KIP-554)仍在讨论阶段,尚未正式落地。

RocketMQ 4.x 提供 18 个固定延迟级别,配置在 Broker 的 messageDelayLevel 属性(1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h),Producer 在消息 property 里设置 delayLevel 即可使用。RocketMQ 5.x 引入基于 TimerWheel 的任意精度延迟(精度可到秒级),不再受 18 档限制,具体配置项为 enableTimerMessageChannel=true,Timer Wheel 相关代码在 org.apache.rocketmq.store.timer 包下。

延迟消息这一场景是选型差异最明显的维度之一:如果业务有大量延迟任务(订单超时、续费提醒、定时推送),Kafka 需要自行搭建延迟层,RocketMQ 开箱即用。

运维与生态复杂度

Kafka 旧版本依赖 ZooKeeper 做元数据存储和 Controller 选举,这意味着生产环境需要同时维护两套有状态集群。自 Kafka 2.8 起 KRaft 模式(KIP-500)进入 Preview,Kafka 3.3 起 KRaft 达到生产就绪状态,3.5 起 ZooKeeper 模式正式废弃,4.0 完全移除 ZooKeeper 依赖。KRaft 模式下 Kafka 只需要维护 Broker 和 Controller 进程(Controller 可以和 Broker 合并),运维复杂度显著降低。

Confluent 平台(包括 Schema Registry、Kafka Connect REST API、KSQL/ksqlDB 等)是 Kafka 生态的重要组成部分,为企业场景提供完整数据管道支撑,但也引入了额外的组件依赖和授权成本。

RocketMQ 的 NameServer 是无状态的元数据注册服务,各 NameServer 节点彼此不同步、不选举,Broker 周期性地向所有 NameServer 上报心跳,Consumer/Producer 从任意 NameServer 拉取路由。这个设计极大降低了控制面的运维难度:NameServer 可以独立重启,不影响 Broker 正常工作,不存在单点故障。代价是 NameServer 的路由信息有最终一致性的短暂延迟(默认 30 秒心跳周期)。

阿里云提供托管版 RocketMQ(云消息队列 RocketMQ 版),屏蔽了 NameServer 和 Broker 的运维细节,适合不想自建基础设施的场景。

选型参考

选 Kafka 的典型场景:

  • 日志采集与流式处理管道(与 Flink、Spark Structured Streaming、Kafka Streams 深度集成)
  • 极高吞吐单 topic 场景(消息体小、partition 数可控)
  • 需要 Schema Registry 等数据契约管理的数据平台
  • 已有 Confluent 或开源 Kafka 生态投入,迁移成本高的存量系统

选 RocketMQ 的典型场景:

  • 需要延迟消息(定时任务、超时订单、分布式定时器)
  • 需要事务消息与本地事务绑定(下单流程的分布式一致性)
  • Topic 数量多、每个 topic 消息量不均匀(CommitLog 架构的优势场景)
  • Java 技术栈为主、运维团队更熟悉 NameServer 模型
  • 阿里云上的托管场景

两者都不是另一个的超集,选型的决定因素是具体业务特征,而不是哪个"更先进"。

可运行实验

创建大量 topic 时,Kafka 和 RocketMQ 的资源消耗表现有明显差异,可以通过以下步骤观察。

Kafka 侧(单节点,KRaft 模式):

1
2
3
4
5
6
7
8
9
10
11
12
# 创建 1000 个 topic,每个 1 partition 1 replica
for i in $(seq 1 1000); do
kafka-topics.sh --bootstrap-server localhost:9092 \
--create --topic test-topic-$i \
--partitions 1 --replication-factor 1
done

# 观察文件描述符数量
lsof -p $(pgrep -f kafka.Kafka) | grep -c "REG"

# 观察 kafka-logs 目录下文件数量
find /tmp/kafka-logs -name "*.log" | wc -l

RocketMQ 侧(单 Broker):

1
2
3
4
5
6
7
8
9
10
11
12
# 创建 1000 个 topic,每个 8 个 queue
for i in $(seq 1 1000); do
mqadmin updateTopic -n localhost:9876 \
-b localhost:10911 \
-t test-topic-$i -r 8 -w 8
done

# 观察 CommitLog 目录大小(不随 topic 数增长)
du -sh ~/store/commitlog/

# 观察 ConsumeQueue 目录(随 topic/queue 数增长)
find ~/store/consumequeue -name "*.dat" | wc -l

实验结果的规律:Kafka 的 kafka-logs 文件数随 topic×partition 线性增长,RocketMQ 的 CommitLog 体积与 topic 数无关,只与消息写入量有关,文件描述符增长集中在 ConsumeQueue 而不在写路径上。

模式提炼

存储布局决定了可用的消费模式,消费模式决定了对哪些业务场景友好。Kafka 选择"一 partition 一文件",换来写路径极简但 topic/partition 数有上限;RocketMQ 选择 CommitLog 集中写、ConsumeQueue 分散索引,换来 topic 数量弹性但写路径多一个间接层。两种架构都是内部约束自洽的——在各自的约束体系内,其他设计选择(事务、延迟、消费模型)都是沿着这个存储决策向外延伸的结果。

工程迁移对照表

Kafka 概念 RocketMQ 对应概念 主要差异
Partition Message Queue CommitLog 共享,ConsumeQueue 独立
Consumer Group Consumer Group Pop 模式下绑定关系更松
Topic Topic RocketMQ topic 可配置 readQueueNum/writeQueueNum 分离
Log Segment (.log) CommitLog 文件段 RocketMQ 所有 topic 共享同一个 CommitLog 目录
Offset Offset / ConsumeQueue 索引 语义相同,存储位置不同
__consumer_offsets Broker 内存 + 文件持久化 RocketMQ offset 由 Broker 管理,不写到独立 topic
Transaction Coordinator Broker 事务状态机 Kafka 有专属节点,RocketMQ 由任意 Broker 处理
KRaft Controller NameServer 职能不同:KRaft 是强一致元数据,NameServer 是最终一致路由

常见误解

“RocketMQ 只是功能更多的 Kafka”

两者的存储模型从根本上不同,分别对不同场景做了专门优化。RocketMQ 在多 topic 场景下写性能更好,Kafka 在单 topic 大 partition 场景下读写效率更高。功能集的差异(延迟消息、Pop 消费)是存储架构差异的自然延伸,而不是简单的功能叠加。

“Kafka 支持事务,所以可以替代 RocketMQ 的所有事务场景”

Kafka 的事务语义是"跨 partition 原子写入",面向流处理(read-process-write 循环)。RocketMQ 的半消息事务面向"本地事务与消息发送的一致性",两种语义解决的问题不同,不能直接互换。Kafka 事务不支持消息与数据库操作的绑定,RocketMQ 半消息通过回查机制做到了这一点。

“NameServer 比 ZooKeeper/KRaft 弱,说明 RocketMQ 可靠性低”

NameServer 是路由目录,不参与 Broker 的主从选举(RocketMQ 4.x 的主从由 Raft 实现,5.x 引入了 DLedger 模式)。NameServer 宕机只影响新连接的路由查询,已建立连接的 Producer/Consumer 仍然正常工作,路由缓存在客户端本地。ZooKeeper/KRaft 在 Kafka 里承担的是元数据持久化和 Controller 选举,职能比 NameServer 重得多,不是同等地位的对比。

“Kafka 去掉 ZooKeeper 之后运维就简单了”

KRaft 模式确实移除了 ZooKeeper 依赖,但 Kafka 集群本身的 Controller 选举、ISR 管理、分区重分配等机制仍然存在,只是元数据存储从 ZooKeeper 迁移到了 Kafka 内部。KRaft 降低的是"需要同时运维两套系统"的成本,不是 Kafka 固有架构复杂度。

练习

  1. 在本地分别启动单节点 Kafka(KRaft 模式)和单节点 RocketMQ,依次创建 100 个 topic,用 lsofdu 对比两者的文件系统占用,解释差异来源。

  2. 用 RocketMQ 的事务消息 API 实现"扣库存 + 发消息"的原子操作:先发半消息,在 executeLocalTransaction 里执行数据库扣库存,在 checkLocalTransaction 里查询数据库状态作为回查响应。尝试在 executeLocalTransaction 中故意抛出异常,观察 Broker 的回查行为。

  3. 在 Kafka 中实现一个简单的延迟消息机制:创建 delay-5s topic,Producer 发消息时附带目标 topic 和预期投递时间,另起一个 consumer 检查时间戳,到期后转发。观察在高 TPS 场景下时间精度如何变化,并与 RocketMQ 5.x 的 TimerWheel 延迟精度对比。

  4. 分别对比两个系统的顺序消息保障:在 Kafka 中向同一 partition 发送 1000 条带序列号的消息,在 RocketMQ 中向同一 queue 发送相同数量的消息,各用 3 个 consumer 实例并发消费,观察接收顺序与发送顺序的一致性。

系列导航

序号 主题
00 导读:为什么 Kafka 的核心是一根日志
01 架构总览:Broker、Controller 与元数据管理
02 日志存储:Segment、Index 与零拷贝
03 Producer 内部机制:攒批、分区与 acks
04 幂等 Producer 与序列号:消息不重不丢的第一层
05 Consumer Group 协议:分配、重平衡与静态成员
06 Offset 管理:提交、重置与消费语义
07 副本与 ISR:高可用的代价和折中
08 Controller 与 KRaft:从 ZooKeeper 到内置共识
09 Exactly-Once 与事务:跨 partition 的原子写入
10 日志压缩:把 topic 当 KV 表用
11 Kafka Streams:在日志之上构建流处理
12 Kafka Connect:标准化的数据管道
13 Schema Registry 与数据治理:给消息加上契约
14 性能模型:吞吐、延迟与调优思路
15 安全体系:认证、授权与加密
16 生产运维:集群扩缩、监控指标与故障排查
17 Kafka 与 RocketMQ:两种消息系统的设计选择

参考资料