数环通消息中间件选型实录:RocketMQ vs Kafka vs RabbitMQ,我们为什么选了RocketMQ

为什么需要消息中间件

先回答一个前置问题:什么时候你需要引入消息中间件?

答案很简单------当你的系统出现以下任何一个痛点时:

  1. 异步解耦:A服务调B服务,B处理很慢,A不想等
  2. 削峰填谷:突发流量打过来,系统一时扛不住,需要排队慢慢处理
  3. 事件驱动:一个动作发生后,需要通知多个下游系统
  4. 延迟执行:某个操作需要在指定时间后触发

我们的iPaaS平台,这四个场景全都有。流程执行日志需要异步写入、Webhook突发事件需要削峰、流程发布需要通知多个子系统、定时触发的流程需要延迟消息。

所以不是"要不要引入"的问题,而是"选哪个"的问题。


候选方案

市面上成熟的消息中间件主要三个:

  • Apache Kafka --- 大数据生态的王者
  • RabbitMQ --- AMQP协议的标杆实现
  • Apache RocketMQ --- 阿里开源的消息引擎

还有Pulsar、NATS、RedPanda等新生代,但考虑到成熟度和社区规模,我们只在这三个里选。


一、Kafka:为吞吐量而生

架构原理

Kafka的设计哲学是"分布式日志"。它的核心抽象是Partition Log------每个Topic被切分成多个Partition,每个Partition是一个有序的、不可变的消息序列,通过追加写入(Append-Only)实现极高的写入吞吐。
Consumer Group
Kafka Cluster
Producers
Broker3
Broker2
Broker1
Replicate
Replicate
Replicate
Producer 1
Producer 2
Topic1-Partition0 Leader
Topic1-Partition1 Follower
Topic1-Partition0 Follower
Topic1-Partition1 Leader
Topic1-Partition2 Leader
Topic1-Partition2 Follower
ZooKeeper / KRaft
Consumer 1
Consumer 2
Consumer 3

复制代码
Topic: order_events
├── Partition 0: [msg1, msg2, msg5, msg8, ...]
├── Partition 1: [msg3, msg4, msg6, msg9, ...]
└── Partition 2: [msg7, msg10, msg11, ...]

核心组件:

  • Broker:存储节点,每个Broker负责多个Partition
  • ZooKeeper/KRaft:集群元数据管理(新版本已可选KRaft模式去ZK)
  • Producer:按分区策略(Round-Robin/Key Hash/自定义)将消息写入Partition
  • Consumer Group:消费者组内每个消费者负责一部分Partition,实现并行消费

存储模型:

Kafka直接利用操作系统的Page Cache和顺序写磁盘,不走JVM堆内存。这是它吞吐量惊人的秘诀------顺序写磁盘的速度可以接近内存随机写。每条消息通过(Topic, Partition, Offset)三元组唯一定位。

消费模型:

Pull模式。Consumer主动从Broker拉取消息,可以控制消费速率、可以重复消费(回放Offset)。

部署方案

最小生产部署:3 Broker + 3 ZooKeeper(或KRaft模式3节点)。

K8s部署:推荐使用Strimzi Operator,自动化管理Kafka集群的扩缩容、滚动升级、证书管理。

资源需求:Kafka对磁盘IO要求高,推荐SSD。内存主要用于Page Cache,建议预留物理内存的60%给OS(而不是给JVM)。

优势

  • 吞吐量天花板级:单集群百万级TPS不是问题
  • 消息持久化能力强:消息可以保留数天甚至数周,支持回放
  • 大数据生态完美整合:Kafka Connect、Kafka Streams、Flink、Spark Streaming原生支持
  • Exactly-Once语义:通过幂等Producer + 事务支持精确一次语义
  • 社区成熟:几乎所有语言都有高质量的客户端

劣势

  • 延迟消息不原生支持:需要自己实现(时间轮/外部存储),或者用第三方方案
  • 消息过滤能力弱:没有Tag/属性级别的服务端过滤,需要消费端自己过滤
  • 运维复杂度高:Partition的Rebalance、ISR管理、Topic的Partition数变更都是坑
  • 单条消息延迟不是强项:设计目标是高吞吐,不是低延迟。P99延迟可能在10ms+
  • Java客户端的API较底层:虽然有Spring-Kafka封装,但配置项多、概念多(Offset提交策略、再均衡监听器等)

典型应用场景

  • 日志收集与分析(ELK/EFK的数据管道)
  • 用户行为追踪(点击流、页面浏览)
  • 大数据实时计算(与Flink/Spark配合)
  • CDC数据同步(Debezium → Kafka → 下游)

二、RabbitMQ:协议最完善的传统消息队列

架构原理

RabbitMQ基于AMQP 0-9-1协议,核心抽象是Exchange + Queue + Binding的路由模型。
Consumers
RabbitMQ Broker
Producers
Queues
Exchanges
routing_key=order.created
routing_key=order.*
Binding: order.created
Binding: order.*
Binding: broadcast
Binding: broadcast
Producer 1
Producer 2
Direct Exchange
Topic Exchange
Fanout Exchange
Queue: order.created
Queue: order.paid
Queue: notification
Consumer 1
Consumer 2
Consumer 3

复制代码
Producer → Exchange --[Binding Rules]--> Queue → Consumer

核心概念:

  • Exchange :消息路由器,决定消息去哪个Queue。四种类型:
    • direct:精确匹配Routing Key
    • topic:通配符匹配(如order.*.created
    • fanout:广播到所有绑定Queue
    • headers:按消息头匹配
  • Queue:消息的实际存储位置,消费者从Queue中拉取/推送消息
  • Binding:Exchange和Queue之间的绑定规则
  • VHost:虚拟隔离,类似数据库的Schema

存储模型:

RabbitMQ默认使用Erlang的Mnesia数据库存储消息元数据,消息体存在自己的消息存储引擎中。持久化消息会先写WAL(Write-Ahead Log),再异步刷盘。

消费模型:

Push模式为主(Broker主动推给Consumer),也支持Pull。Push模式的好处是实时性好,坏处是Consumer来不及处理时需要做流控(prefetch_count)。

部署方案

最小生产部署:3节点镜像队列(Mirrored Queue)或Quorum Queue集群。

K8s部署:官方提供RabbitMQ Cluster Operator。

资源需求:Erlang VM对内存使用有自己的管理策略。默认当内存使用超过物理内存40%时触发流控。磁盘空间至少预留2GB告警阈值。

优势

  • 路由能力最灵活:Exchange四种模式 + Binding可以实现任意复杂的消息路由
  • 协议支持最广:AMQP、MQTT、STOMP、WebSocket全支持
  • 单条消息延迟低:微秒级延迟,适合金融/实时通知场景
  • 管理界面友好:自带Web Management UI,队列/连接/通道可视化
  • 成熟度极高:2007年发布,经历了十几年生产验证
  • 延迟消息支持:通过rabbitmq-delayed-message-exchange插件支持(但不是原生)

劣势

  • 吞吐量有上限:单节点万级TPS是上限,远低于Kafka和RocketMQ
  • 消息堆积能力弱:设计假设是消息被快速消费。堆积超过阈值后性能急剧下降
  • 集群扩展性有限:镜像队列本质是复制,不是分片。Quorum Queue改善了但仍有限制
  • Erlang生态:对Java团队不太友好。出问题时排查Erlang运行时不容易
  • 不适合大数据场景:没有消息回放能力,消费完就没了(除非做死信队列)

典型应用场景

  • 传统企业应用集成(遵循AMQP标准的系统对接)
  • 微服务间的异步通信(特别是需要复杂路由的场景)
  • IoT消息处理(MQTT协议支持)
  • 实时通知推送(低延迟Push模式)

三、RocketMQ:为互联网业务设计的消息引擎

架构原理

RocketMQ的设计借鉴了Kafka的Partition模型,但在业务消息处理能力上做了大量增强。
Consumers
Broker Group B
Broker Group A
NameServer Cluster
Producers
Route Discovery
Route Discovery
Send Message
Send Message
Register
Register
Register
Register
Sync
Sync
Push/Pull
Push/Pull
Read when Master down
Producer 1
Producer 2
NameServer 1
NameServer 2
Broker-A Master
Broker-A Slave
Broker-B Master
Broker-B Slave
Consumer Group 1
Consumer Group 2

复制代码
Producer → NameServer(路由发现)→ Broker(存储)→ Consumer

核心组件:

  • NameServer:轻量级路由注册中心,无状态,替代ZooKeeper。每个NameServer独立持有全量路由信息。
  • Broker:消息存储和投递。支持Master-Slave或DLedger(Raft)模式
  • Topic:逻辑消息分类
  • MessageQueue:类似Kafka的Partition,实际存储单元
  • Tag :消息二级分类,服务端过滤(这是RocketMQ的独特优势)

存储模型:

RocketMQ采用CommitLog + ConsumeQueue双层架构:

  • CommitLog:所有消息顺序写入一个文件(不区分Topic),保证写入性能
  • ConsumeQueue:按Topic+Queue维度建立的索引,记录消息在CommitLog中的位置

Read Path
Index Build
Write Path
Message
CommitLog 顺序追加写入
ConsumeQueue Topic-A Queue-0
ConsumeQueue Topic-A Queue-1
ConsumeQueue Topic-B Queue-0
IndexFile 按Key/时间检索
Consumer 按Offset读取
Consumer 按Offset读取

这种设计的巧妙之处:写入是完全顺序的(所有Topic混写一个文件),读取通过ConsumeQueue索引定位,兼顾了写入吞吐和读取效率。

消费模型:

Pull为主(长轮询模式),兼具Push的实时性和Pull的流控能力。Consumer通过长轮询(Long Polling)实现"看起来像Push但实际是Pull"的效果。

部署方案

最小生产部署:2个NameServer + 2组Broker(Master-Slave)。

K8s部署:官方提供RocketMQ Operator。也可以直接用阿里云的商业版本(完全托管)。

资源需求:相比Kafka更轻量。NameServer几乎不消耗资源(512MB内存即可),Broker根据消息量配置,中等规模4C8G足够。

优势

  • 延迟消息原生支持:RocketMQ 5.0支持任意时间的精确延迟投递,不需要插件、不需要第三方方案
  • 消息类型丰富:普通消息、顺序消息(FIFO)、延迟消息、事务消息------全部原生支持
  • Tag过滤:服务端按Tag过滤消息,Consumer只接收感兴趣的消息,减少无效网络传输
  • Java生态友好:纯Java实现,客户端API设计清晰,Spring Boot集成方便
  • 运维简单:NameServer无状态、Broker配置简洁、自带Dashboard
  • 消息追踪:原生支持消息轨迹查询------这条消息什么时候发的、谁消费了、消费状态是什么
  • 吞吐量够用:单机十万级TPS,不如Kafka但足以应对大多数业务场景
  • 私有化部署友好:不依赖ZooKeeper,组件少,适合客户环境部署

劣势

  • 大数据生态较弱:跟Flink/Spark的集成不如Kafka成熟
  • 海外社区相对小:英文资料和国际社区不如Kafka和RabbitMQ丰富
  • 极端吞吐场景不如Kafka:日志类、CDC类超大数据量场景Kafka更合适
  • 部分高级功能需要商业版:阿里云版本有些增强特性开源版没有

典型应用场景

  • 电商交易链路(订单、支付、物流的异步解耦)
  • 定时/延时任务(订单超时关闭、延迟通知)
  • 分布式事务(半消息 + 事务回查)
  • 业务消息的精确分发(Tag过滤 + 顺序消息)

四、三者深度对比

4.1 架构对比

维度 Kafka RabbitMQ RocketMQ
语言 Scala/Java Erlang Java
协议 自定义二进制 AMQP/MQTT/STOMP 自定义(兼容gRPC)
注册中心 ZK/KRaft 内置Mnesia NameServer(无状态)
存储模型 Partition Log Queue(Mnesia/Khepri) CommitLog + ConsumeQueue
消费模式 Pull Push为主 长轮询(准实时Pull)
消息模型 Topic-Partition Exchange-Queue-Binding Topic-Queue-Tag

4.2 消息能力对比

能力 Kafka RabbitMQ RocketMQ
延迟消息 不支持(需自实现) 插件支持 原生支持(任意精度)
顺序消息 Partition级有序 单Queue有序 Queue级有序
事务消息 Producer事务 不支持 半消息+回查
消息过滤 不支持(客户端过滤) Routing Key Tag服务端过滤
消息回溯 支持(按Offset) 不支持 支持(按时间)
死信队列 不原生 支持 支持
消息轨迹 不原生 不支持 原生支持
广播消费 Consumer Group Fanout Exchange 广播模式

4.3 性能对比

指标 Kafka RabbitMQ RocketMQ
单机吞吐 百万级TPS 万级TPS 十万级TPS
消息延迟 毫秒级(10ms+) 微秒级 毫秒级(2-5ms)
消息堆积 极强(磁盘为限) 弱(堆积后性能下降) 强(亿级堆积)
Topic数量影响 Topic多时性能下降 Queue多影响有限 Topic多时性能稳定

4.4 运维与易用性

维度 Kafka RabbitMQ RocketMQ
部署复杂度 高(ZK+Broker) 中(Erlang运行时) 低(NameServer+Broker)
管理界面 需第三方(Kafka Eagle等) 自带Web UI 自带Dashboard
监控 JMX + Prometheus 自带 + Prometheus JMX + Prometheus
学习曲线 陡(概念多) 中(AMQP需理解) 缓(概念直观)
Java开发体验 中(配置多) 中(Spring AMQP) 好(API简洁直观)
私有化友好度 低(依赖多) 中(Erlang) 高(纯Java,组件少)

五、我们为什么选了RocketMQ

说完理论,回到我们的实际场景。选型的决策不是"谁最强",而是"谁最适合"。

决策因素1:延迟消息是刚需

我们的iPaaS平台有大量延迟执行场景:

  • 流程延时恢复:流程执行到某一步暂停,等待外部回调,超时后自动恢复
  • 定时触发:用户设置"每天9:00执行",需要在精确时间触发
  • 重试退避:API调用失败后延迟重试(3s → 9s → 27s递增)

这些场景对延迟消息的精确度和灵活性要求很高。看一下我们实际的使用方式:

java 复制代码
// 延迟消息------指定精确的投递时间戳
public SendReceipt sendMessage(String topic, String tag, String msgBody, 
                               String msgKey, long deliveryTimestamp) {
    final Message message = new MessageBuilderImpl()
            .setTopic(topic)
            .setKeys(msgKey)
            .setTag(tag)
            .setDeliveryTimestamp(deliveryTimestamp)  // 精确到毫秒
            .setBody(msgBody.getBytes(StandardCharsets.UTF_8))
            .build();
    return producer.send(message);
}

RocketMQ 5.0的延迟消息支持任意时间精度的延迟投递。不是Kafka那样需要自己做时间轮、不是RabbitMQ那样需要装插件、不是只支持固定的延迟等级------而是你传一个时间戳,它就在那个时间精确投递。

这一个特性,在我们的选型中权重最高。

决策因素2:多消息类型一站式覆盖

看我们MessageSendManager实际封装的四种发送模式:

java 复制代码
// 1. 普通消息(带消息组,保证同组顺序投递)
public SendReceipt sendMessage(String topic, String tag, String msgGroup, 
                               String msgBody, String msgKey)

// 2. FIFO顺序消息(严格有序)
public SendReceipt sendFifoMessage(String topic, String tag, String msgGroup, 
                                   String msgBody, String msgKey)

// 3. 异步消息(Fire-and-forget,不阻塞主线程)
public void sendMessageAsync(String topic, String tag, String msgBody, String msgKey)

// 4. 延迟消息(精确时间投递)
public SendReceipt sendMessage(String topic, String tag, String msgBody, 
                               String msgKey, long deliveryTimestamp)

四种消息类型,一个SDK全覆盖。不需要集成多个中间件、不需要额外插件。

我们的实际使用场景:

  • 普通消息:流程执行日志异步写入、计费消耗事件分发
  • 顺序消息:订单状态变更(必须按顺序处理)
  • 异步消息:非关键路径的通知(如流程执行完成通知)
  • 延迟消息:流程定时触发、超时恢复、重试退避

决策因素3:Java生态友好度

团队是Java技术栈。RocketMQ纯Java实现,出问题时可以直接看源码排查------这在生产环境中非常重要。

对比三者的Java开发体验:

Kafka的Java客户端:

java 复制代码
// 配置项多、概念重
Properties props = new Properties();
props.put("bootstrap.servers", "...");
props.put("key.serializer", "...");
props.put("value.serializer", "...");
props.put("acks", "all");
props.put("retries", 3);
props.put("batch.size", 16384);
props.put("linger.ms", 1);
props.put("buffer.memory", 33554432);
// 还有几十个配置项...
KafkaProducer<String, String> producer = new KafkaProducer<>(props);

RabbitMQ的Java客户端:

java 复制代码
// 需要理解AMQP的Exchange/Queue/Binding概念
ConnectionFactory factory = new ConnectionFactory();
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare("exchange", "topic", true);
channel.queueDeclare("queue", true, false, false, null);
channel.queueBind("queue", "exchange", "routing.key");
channel.basicPublish("exchange", "routing.key", null, body);
// 概念链路长、出错不易定位

RocketMQ的Java客户端:

java 复制代码
// 概念清晰、API简洁
Message message = new MessageBuilderImpl()
    .setTopic("topic")
    .setTag("tag")
    .setKeys("key")
    .setBody(body)
    .build();
SendReceipt receipt = producer.send(message);

Topic → Tag → Key → Body,概念链路极短。新人5分钟就能理解并写出生产级代码。

决策因素4:Tag服务端过滤

我们的iPaaS平台,一个Topic下会有多种事件类型。比如engine_topic下有:流程启动、流程完成、步骤执行、步骤失败、流程超时...

如果用Kafka,Consumer必须接收所有消息,在客户端自己过滤------浪费带宽和CPU。

RocketMQ的Tag过滤在Broker侧完成:

java 复制代码
// 消费者只订阅特定Tag的消息
consumer.subscribe("engine_topic", "FLOW_COMPLETED || FLOW_TIMEOUT");

Broker只推送匹配Tag的消息给Consumer,不匹配的不传输。在我们日均千万级消息量的场景下,这能显著减少网络传输和消费端的处理压力。

决策因素5:私有化部署友好

我们的产品要支持私有化部署(客户内网环境)。中间件的选择必须考虑客户环境的约束。

  • Kafka需要ZooKeeper(虽然KRaft在逐步推进,但生产稳定性还需观望),部署组件多
  • RabbitMQ需要Erlang运行时,客户环境可能没有
  • RocketMQ只需要JDK + NameServer + Broker,客户环境大概率有JDK

我们的K8s私有化部署配置:

yaml 复制代码
ENV ROCKETMQ_ROCKET_MQ_TYPE=primitive
ENV ROCKETMQ_ENDPOINTS=rocketmq-broker-svc.default.svc.cluster.local:8081
ENV ROCKETMQ_NAMESERVER=rocketmq-nsr-svc.default.svc.cluster.local:9876

两个服务、两个端口,完事。不需要额外的ZooKeeper集群、不需要Erlang运行时。


六、RocketMQ在我们系统中的实际使用全景

部署完成后,RocketMQ在我们平台中承担了以下职责:

复制代码
Topic层面划分:
├── engine_flow_running_topic    --- 流程执行事件(最高频)
├── push_event_topic             --- Webhook事件推送
├── push_event_order_topic       --- 有序事件推送
├── market_topic                 --- 计费/实例变更事件
├── flow_delay_resume_topic      --- 流程延迟恢复(延迟消息)
├── pubsub_topic                 --- 发布/订阅触发器
└── digiwin_request_topic        --- 开放平台异步转发

每个Topic通过Tag进一步细分:

java 复制代码
public enum MqTagEnum {
    FLOW_RUNNING_LOG_TAG,           // 流程运行日志
    FLOW_RUNNING_STEP_LOG_TAG,      // 步骤运行日志
    INSTANCE_INITIALIZED_TAG,       // 实例初始化完成
    INSTANCE_FEATURE_RUNNING_TAG,   // 功能消耗
    TOKEN_REFRESH_TAG,              // Token刷新
    // ...
}

七、几个选型时容易忽略的点

1. 消息队列不是越快越好

很多人选型时盯着TPS比------Kafka百万级、RocketMQ十万级、RabbitMQ万级。但你的业务真的需要百万TPS吗?

我们日均千万级消息,平摊到每秒是~115 TPS。峰值可能10倍,也就是~1200 TPS。三个方案都能轻松覆盖。在这个量级下比较TPS毫无意义,比较的应该是功能适配度和运维成本

2. 延迟消息比你想象的重要

一开始我们低估了这一点。"大不了自己做个定时任务扫描"------后来发现这条路走不通:

  • 扫描间隔1s → 精度不够,有些场景需要毫秒级
  • 扫描间隔100ms → 数据库扛不住(百万条待执行记录)
  • 用Redis的ZSET做延迟队列 → 可靠性不够(Redis持久化有丢数据风险)

最终还是回到消息队列的延迟消息能力。RocketMQ原生支持,省了我们大量的自研成本。

3. 运维能力要匹配

选型不只是看技术指标,还要看你的运维团队能不能Hold住

Kafka的运维知识体系很深------ISR、HW、LEO、Rebalance、Partition Reassignment------不是看两篇博客就能掌握的。出了问题(如Consumer频繁Rebalance导致消费延迟),排查需要很深的理解。

RocketMQ的运维相对简单------NameServer无状态随便重启、Broker主从切换自动化、Dashboard查消息轨迹很直观。对于我们这种"研发兼运维"的小团队来说,运维友好度是实打实的生产力。

4. 社区和人才储备

选一个中间件,意味着未来几年你都要跟它打交道。遇到问题时能不能找到人问?招人时候选人会不会用?

在国内Java开发圈,RocketMQ的使用率和熟悉度明显高于RabbitMQ。大厂出来的开发者大概率用过RocketMQ,能快速上手。这是一个"隐性成本"------看起来不影响技术决策,但在招聘和团队扩张时会体现出来。


八、什么场景该选什么

你的场景 推荐 原因
大数据/日志/CDC Kafka 超高吞吐 + 大数据生态集成
IoT/金融/复杂路由 RabbitMQ MQTT支持 + 微秒延迟 + 灵活路由
电商/SaaS/业务消息 RocketMQ 延迟消息 + 事务消息 + Tag过滤 + Java友好
混合场景 Kafka + RocketMQ 日志走Kafka,业务走RocketMQ

没有"最好的消息队列",只有"最适合你场景的"。我们选RocketMQ,核心原因总结就三条:

  1. 延迟消息原生支持------解决了流程定时触发和超时恢复的刚需
  2. Java生态友好------纯Java实现、API简洁、团队上手快
  3. 私有化部署简单------组件少、不依赖额外运行时、客户环境适配容易

这三条在我们的权重矩阵中占了70%以上。技术选型永远是"在你的约束条件下选最合适的",而不是"选社区评分最高的"。

相关推荐
huaiixinsi1 小时前
Canal + Outbox、Kafka 选型与高可用、Caffeine 底层原理总结
java·数据库·分布式·mysql·spring·adb·kafka
许长安1 小时前
Kafka 架构讲解:从提交日志到分区副本机制
c++·经验分享·笔记·分布式·架构·kafka
qq_297574671 小时前
第十二篇:RabbitMQ消息积压问题——排查与解决方案(实战优化)
分布式·rabbitmq
qq_297574671 小时前
第十三篇:RabbitMQ限流与熔断——保护系统稳定性
分布式·rabbitmq·ruby
菜鸟小九1 小时前
Kafka()
分布式·kafka
qq_2975746715 小时前
第十四篇:RabbitMQ监控与日志分析——快速排查线上问题
分布式·rabbitmq·ruby
小的~~15 小时前
CentOS7安装CDH6.3.2
hive·hdfs·kafka
阿萨德528号20 小时前
Windows RabbitMQ 启动完整指南(附启动报错解决、如何以服务方式后台运行)
windows·rabbitmq·ruby
敏君宝爸1 天前
RabbitMQ多线程消费与死信队列方案
分布式·rabbitmq