RocketMQ、Kafka、RabbitMQ 怎么选
从三者的本质定位出发——RabbitMQ 是消息代理、Kafka 是分布式日志、RocketMQ 是业务消息平台——沿着消息模型、吞吐量级、业务语义(顺序/事务/延迟/回溯)、堆积可靠性和生态运维五个维度对比,给出一套可复用的四步选型决策。
「我们要上消息队列,用哪个?」这个问题最常见的答案是「高吞吐就 Kafka,业务消息就 RocketMQ,简单点就 RabbitMQ」。这句话方向对,却几乎没用——它没告诉你在具体约束下怎么判断,也掩盖了一个更根本的问题:三个系统不是“三种档次的同一个东西”,而是三种定位。
RabbitMQ 本质是一个消息代理(broker):它管的是路由——消息按规则分发到队列,被消费者取走就删。Kafka 本质是一个分布式日志(log):它管的是事件流——消息 append 进日志、可回放、多个订阅者各自读自己的位置。RocketMQ 本质是一个业务消息平台:它在日志模型上补了业务消息真正要的东西——事务消息、延迟消息、重试与死信。
站内《消息队列怎样削峰》的结尾留了一个钩子:选型要根据顺序、回放、路由、延迟、吞吐、运维体系和云服务约束来判断,而不是一句话结束。这篇文章就把这七个约束拆开,落到三个系统上,最后给一套能直接用的决策顺序。结论先说:先判断你的消息是“任务”还是“事件流”,再看吞吐量级和特殊语义,最后让生态和团队拍板。
一、三个系统是三种定位,不是三档性能
选型的第一步不是比参数,而是先看清“你在选一个什么东西”。
RabbitMQ 实现 AMQP 协议,核心是 exchange、queue、binding 三级结构。消息从 exchange 按 routing key 匹配到 queue,消费者从 queue 取,消费后 ack 删除。它的强项是灵活路由:direct、topic、fanout、headers 四种交换机,配合优先级、TTL、死信交换,能把消息精准送到该去的地方。它适合的是“任务分发”——一条消息对应一件要被完成的工作,完成后消息使命结束。
Kafka 的核心是 topic、partition 和 append-only log。消息写进分区日志,按 offset 顺序追加,消费者用 offset 记录自己读到哪,读过的消息不会删除(保留一段时间)。它的强项是事件流——一条消息是一个事实,可以被多个消费者独立读取、可以回放、可以重算。它适合的是“事件溯源、数据管道、日志采集”这类“要反复读、要多方读”的场景。
RocketMQ 介于两者之间,但更偏向业务。它的 topic + queue 结构和 Kafka 很像,却把精力花在业务消息需要的语义上:原生事务消息、18 级延迟消息(5.x 起支持任意时间定时消息)、重试队列和死信队列。它适合的是“订单、支付、库存”这类对投递语义要求高的业务消息。
这张图的价值在于提醒你:如果需求是“一个任务只被一个消费者处理完”,RabbitMQ 和 RocketMQ 是自然选择,Kafka 也能做但要用消费组去模拟“取走”语义,显得别扭;如果需求是“一条事件要能被回放、被多个团队各自消费”,Kafka 是自然选择,RabbitMQ 消费即删的模型根本做不到。定位错了,后面所有调参都是补丁。
当然,三者的能力有重叠:Kafka 也能做任务(用消费组),RocketMQ 也能做流(按时间回溯),RabbitMQ 也能做事件(用 fanout exchange)。重叠区意味着“选错”通常不会立刻爆炸,只会随着规模变大越来越别扭。定位的作用不是画一条非此即彼的界线,而是告诉你:当你的需求恰好落在某个系统的“主场”上,你得到的是免费的正确性;落在“客场”上,你得自己补逻辑、自己扛代价。
二、消息模型:路由、分区与日志
三个系统的消息模型决定了“消息怎么被组织、怎么被找到、怎么被并行消费”。
| 维度 | RabbitMQ | Kafka | RocketMQ |
|---|---|---|---|
| 核心结构 | exchange → queue → consumer | topic → partition → consumer group | topic → queue → consumer group |
| 消息路由 | 服务端按 routing key + 交换机类型路由 | 无服务端路由,分区内顺序读 | 按 tag / SQL 过滤 |
| 消费模型 | push 或 pull(下一篇展开) | pull(分区分配给组内实例) | push 或 pull(组内负载均衡) |
| 消息删除 | 消费 ack 后删除 | 按保留时间,不随消费删除 | 按保留时间,可手动删除 |
| 并行单位 | 队列(多消费者并发取同一队列) | 分区(一分区一实例) | 队列(组内实例均分队列) |
三者的差异在代码里一目了然:
// RabbitMQ:路由在服务端,用 exchange + binding 决定消息去哪
channel.exchangeDeclare("order", "topic");
channel.queueBind("order.paid", "order", "paid.#");
channel.basicPublish("order", "paid.42", null, body);
// Kafka:无服务端路由,写 topic,消费者按分区拉取
producer.send(new ProducerRecord<>("orders", key, body));
records = consumer.poll(Duration.ofMillis(100));
// RocketMQ:写 topic + tag,消费者按 tag 过滤
producer.send(new Message("orders", "paid", body));
consumer.subscribe("orders", "paid");
RabbitMQ 的路由是三者里最灵活的:同一个消息,通过 exchange 和 binding 能实现广播、按主题订阅、按 header 分发。但这份灵活有代价——它不提供“顺序”这个强保证:一个队列多消费者并发消费时,顺序就乱了。
Kafka 和 RocketMQ 的分区模型则把“并行”和“顺序”绑在同一个单位上:一个分区(或队列)内有序,一个分区同一时刻由一个消费者实例处理。所以它们天然适合“按 key 保序”的场景,代价是分区数决定了并行度上限。
三、吞吐与延迟:量级差异从哪来
三者吞吐的差别是数量级的,但数字要先说清边界——都受机器、消息大小、副本策略和是否批量影响,下面说的是量级和趋势。
Kafka 的吞吐最高,单机百万级消息/秒的量级。它靠三件事:顺序写(append-only log 不用随机写盘)、零拷贝(消息从磁盘到网络不经过用户态多次拷贝)、批量(生产者和消费者都按批处理)。代价是延迟不那么稳定,端到端延迟在毫秒到几十毫秒量级。
RocketMQ 的吞吐在十万级消息/秒,比 Kafka 低一个量级,但延迟更可控,尤其小消息场景能做到毫秒级且稳定。它同样是顺序写 + 批量,但更重视消息的可靠落盘(刷盘策略可配)和投递语义,这些都会吃掉一部分吞吐。
RabbitMQ 的吞吐在万级消息/秒。它是 Erlang 写的、以“灵活路由 + 可靠投递”为设计目标,消息要走 exchange 匹配、进队列、再投递,路径比“顺序追加日志”长得多;持久化消息还要逐条写盘。它天生不是为“百万级吞吐”设计的,硬要扛高吞吐会很吃力。
所以“吞吐”这一项的真实用法是:先估量级,再决定要不要因为吞吐排除某个选项。万级消息/秒,三个都行;十万级,RabbitMQ 开始吃力,Kafka/RocketMQ 合适;百万级,只有 Kafka 是舒服的。
量级怎么估?别只看峰值 QPS,要看“峰值消息速率 × 消息大小 × 副本数”。举一个具体例子:一个日活 100 万的应用,每个用户每天平均产生 50 条业务消息、每条 1KB,均匀分布到 8 小时,平均约 1700 条/秒;但大促式峰值可能冲到 2 万条/秒、持续几分钟。这时 RabbitMQ 的万级吞吐开始吃紧,Kafka 和 RocketMQ 还有余量。反过来,一个内部系统的任务队列一天就几万条消息,用 Kafka 是拿大炮打蚊子——吞吐白费,运维成本却成了负担。估量级,是为了别在“够用”和“杀鸡用牛刀”之间摆错位置。
四、业务语义的分水岭:顺序、事务、延迟、回溯
吞吐是“能不能扛住”,业务语义是“扛住之后能不能用”。这里才是三个系统真正拉开差距的地方。
| 能力 | RabbitMQ | Kafka | RocketMQ |
|---|---|---|---|
| 顺序消息 | 单队列可保序,多消费者并发会乱 | 分区内有序 | 队列内有序,原生顺序消息 |
| 事务消息 | 无(AMQP 事务性能差,不实用) | 有事务,语义是跨分区读写原子 | 原生半消息 + 回查 |
| 延迟/定时消息 | 插件或 TTL+DLX 曲线实现 | 无原生支持 | 原生 18 级,5.x 支持任意时间 |
| 消息回溯 | 不支持(消费即删) | 天然支持(offset 回退重读) | 支持按时间/offset 回溯 |
| 优先级 / TTL / 死信 | 原生且灵活 | 不支持优先级 | 重试队列 + 死信队列内置 |
事务消息是 RocketMQ 独有的杀手锏。它用“半消息 + 状态回查”解决“本地事务和消息发送的一致性”:
TransactionMQProducer producer = new TransactionMQProducer("order-group");
producer.setTransactionListener(new TransactionListener() {
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
return doLocalTx(msg) ? COMMIT_MESSAGE : ROLLBACK_MESSAGE; // 本地事务与半消息一起走
}
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
return queryLocalTx(msg) ? COMMIT_MESSAGE : ROLLBACK_MESSAGE; // Broker 回查事务状态
}
});
producer.sendMessageInTransaction(msg, null);
流程是:先发一条对消费者不可见的半消息,然后执行本地事务,提交后把半消息变成可见;如果本地事务状态不明(进程崩溃了),Broker 会定期回查生产者“这笔事务到底成没成”。这让“下单同时发消息”不需要依赖分布式事务,也不用担心“数据库提交了、消息却没发出去”的双写窗口。Kafka 的事务是另一回事——它保证的是“跨分区的读写原子性”和“幂等生产”,适合流处理里的 exactly-once,不是业务侧的“事务消息”。RabbitMQ 没有实用的事务消息方案。
顺序消息这个点容易被高估,也容易被忽略。Kafka 和 RocketMQ 的分区内/队列内有序,靠的是“同一 key 进同一分区、一分区一个消费者实例”;RabbitMQ 单队列有序,但一旦为了吞吐开多消费者并发,顺序就没了。真正的顺序需求(同一订单的状态变更必须按序处理)用 Kafka/RocketMQ 的 key 路由就能满足;但“全局顺序”(所有消息严格一条龙)在三个系统里都很贵——它等于把并行度压到 1。选型前要先问清楚:顺序到底是“同一业务对象有序”还是“全局有序”,前者常见且好解,后者往往说明模型设计本身有问题。
延迟消息是另一个可能直接决定选型的点。RocketMQ 原生支持:消息带一个延迟级别(或 5.x 的定时时间戳)发出去,Broker 到点才投递,18 个默认级别覆盖 1 秒到 2 小时,5.x 起还能指定任意时间。RabbitMQ 没有原生支持,常用做法是用 rabbitmq-delayed-message-exchange 插件,或者更老的办法——消息发到带 TTL 的队列、过期后转投死信队列,再用一个消费者从死信队列取回“到期的消息”。这套 TTL+DLX 曲线救国能用,但延迟精度和可观测性都差一截,“到期的消息”和“真正的死信”混在一个队列里,管理容易出错。Kafka 则完全不支持,要自己做时间轮或引入二级延迟 topic,等于自己造半个 RocketMQ。
消息回溯是 Kafka 的主场:消息按 offset 保留,消费者随时把 offset 回退到某个位置重读,事件溯源、数据重算、新消费者从历史某一点开始消费,都靠它。RocketMQ 也支持按时间或 offset 回溯,但受保留期限制,过了保留期的历史就没了。RabbitMQ 消费即删,天然不支持回溯——如果需求里有“把三天前的消息再消费一遍”“新系统上线补算历史事件”,RabbitMQ 直接出局。
五、堆积、可靠性与生态运维
堆积能力决定了系统在消费跟不上时会不会崩。Kafka 和 RocketMQ 都是磁盘顺序写,堆积几亿条消息不影响吞吐,只是占磁盘、延迟变长;RabbitMQ 的堆积会显著拖垮性能。所以“可能长时间堆积”的场景,基本排除 RabbitMQ。
RabbitMQ 堆积会变慢的原因值得单独说一句:它的队列在高堆积时会频繁在内存和磁盘之间换页,持久化消息堆积越多,投递一条消息越可能触发磁盘随机读,吞吐断崖式下降。RabbitMQ 官方也建议把队列控制在“消息能被及时消费”的范围内,而不是把它当长期存储。相比之下,Kafka 和 RocketMQ 的堆积只是“日志变长”,消费新消息仍然是顺序读,性能几乎不随堆积变化——这才是它们敢说“堆积亿级不慌”的底气。
可靠性三者都成熟,但确认路径不同。RabbitMQ 用 publisher confirm(发布方确认 broker 接管)+ consumer ack(消费方确认处理完)+ 持久化 + 镜像队列或 quorum queue;Kafka 用生产者 acks 等级(0 / 1 / all)+ 副本 ISR + 幂等 producer;RocketMQ 用同步/异步刷盘 + 主从复制 + 重试队列 + 死信队列。三者的默认投递语义都是 at-least-once——消息可能重复、但(正常配置下)不会静默丢失——配合业务幂等消费,没有谁天生“更可靠”。真正的差别在“确认发生在哪一步”:RabbitMQ 的确认链在 broker 和消费者之间,Kafka 的确认链压在副本同步上,RocketMQ 还多了一层可选的刷盘策略。要“更可靠”不是选一个产品名,是理解它每一段确认到底承诺了什么。
生态与运维是选型里最容易被忽略、却最影响落地的一项。RabbitMQ 是 AMQP 标准、多语言客户端最全、插件丰富、管理界面好用、单机部署一条命令,运维最轻。Kafka 生态最大——流处理、Connector、数据管道都围着它转,但运维最重:集群调优、分区规划、副本管理都要专人。RocketMQ 在国内生态强、阿里系集成方便,但组件多(NameServer、Broker、可选 Controller),运维成本介于两者之间。团队是否已经熟悉某套体系、云上有没有托管服务,往往比一个 benchmark 数字更该决定选型。
六、怎么拍板:四步决策
把前面的维度收拢成四个问题,按顺序问,比拉一张几十行的对比表有用。
第一步,消息是任务还是事件流? 一件要被某个消费者处理完的工作(订单处理、发短信、任务调度),是任务;一条要保留、要回放、要多个团队各自消费的事实(日志、埋点、状态变更),是事件流。任务优先 RabbitMQ / RocketMQ,事件流优先 Kafka。这一步能把一半的选型定下来。
第二步,吞吐到什么量级? 万级以下,三个都行,选运维最省的;十万级,排除 RabbitMQ;百万级,只有 Kafka 舒服。量级估错,其他维度都白看。
第三步,有没有硬性的业务语义? 事务消息、延迟消息 → RocketMQ;复杂路由、优先级、TTL → RabbitMQ;消息回溯、事件重算 → Kafka。这里列的是“硬需求”,不是“锦上添花”——如果只是偶尔要个延迟,RabbitMQ 插件也能凑合,就不必为它换 RocketMQ。
第四步,生态、团队和云。 团队熟不熟、多语言客户端齐不齐、云上有没有托管、周边工具(监控、Connector、流处理)够不够。前三步选出来的“技术正确”答案,常常在这一步被“运维现实”修正。
把这四步跑在三个真实场景上,结论会很清晰。场景一:订单系统,峰值十万级,要事务消息(下单和发消息一致)和延迟消息(30 分钟未支付关闭订单),团队是阿里技术栈——第一步是任务、第二步十万级、第三步要事务和延迟,答案直接是 RocketMQ。场景二:埋点采集,每天十亿级事件、要回放重算、流处理团队都在用 Kafka 生态——第一步是事件流、第二步百万级、第三步要回溯,答案直接是 Kafka。场景三:内部工单系统,一天几万条、要按部门灵活路由、运维只有一个人——第一步是任务、第二步万级以下、第三步只要简单路由,答案倾向 RabbitMQ。三个场景把四个问题各问一遍,选型就从“感觉”变成了“推导”。
七、几个常见误判
「高吞吐就 Kafka」。 吞吐只是四个维度之一。一个十万级消息/秒、但要事务消息和延迟消息的业务,硬上 Kafka 就得自己补事务消息和延迟消息,成本比直接用 RocketMQ 高得多。Kafka 的正确标签是“事件流/日志/回放”,不是“高吞吐”。
「RocketMQ 就是国产 Kafka」。 模型像,定位不像。RocketMQ 的护城河是业务语义(事务/延迟/重试死信),不是“复刻 Kafka 的吞吐”。拿 RocketMQ 去扛 Kafka 那套百万级日志管道,是拿短处撞长处。
「RabbitMQ 吞吐低所以不选」。 万级消息/秒对绝大多数业务已经够了,RabbitMQ 换来的是最灵活的路由、最成熟的协议生态、最轻的运维。吞吐够用之后,这些才是天天打交道的东西。
「先选型,迁移以后再说」。 消息系统一旦上线,迁移意味着双写、消费位点对齐、历史消息搬运,成本远高于数据库换型。选型时的“生态”和“团队”权重应该给足,因为选错了很难回头。
「先小规模上 RabbitMQ,大了迁 Kafka」。 这句话听起来稳妥,实际把迁移成本当成了零。更现实的是:定位不变、只是换实现的迁移(比如 RabbitMQ 迁 RocketMQ,任务型语义相近)相对可行;定位变了的迁移(RabbitMQ 迁 Kafka,从“任务”换成“事件流”)几乎等于重写消费模型。渐进式选型只在“定位不变”时成立,定位变了,“渐进”就是两次建设。所以第一步“消息是任务还是事件流”要先答对,别指望用迁移来修正它。
八、落到一张决策表
| 你的场景 | 建议 | 关键原因 |
|---|---|---|
| 任务分发、灵活路由、低吞吐 | RabbitMQ | 路由强、运维轻、协议标准 |
| 订单/支付/库存,要事务与延迟消息 | RocketMQ | 原生事务消息、延迟消息、重试死信 |
| 日志采集、埋点、数据管道、事件溯源 | Kafka | 高吞吐、可回放、生态大 |
| 流处理、多团队消费同一事件流 | Kafka | 独立消费位点、Connector 生态 |
| 十万级吞吐 + 业务消息 + 国内生态 | RocketMQ | 吞吐够、语义全、集成顺 |
| 团队只熟一套、运维资源有限 | 沿用团队熟悉的 | 迁移成本 > 技术差异 |
选型不是选“最好的消息队列”,是选“最匹配你约束的那个”。把“消息是任务还是事件流、吞吐多少、要不要特殊语义、生态熟不熟”这四个问题答清楚,答案通常就出来了。剩下的,就是别让“高吞吐就 Kafka”这种一句话结论替你思考。
如果系统已经在用某个 MQ,什么时候该考虑迁?信号是三个:吞吐已经逼近当前产品的能力上限且优化无望;业务开始频繁需要当前产品缺失的硬语义(比如在 Kafka 上反复自己实现延迟消息);团队在同时维护多个 MQ、运维成本明显不划算。迁移的目标不是“换个更快的”,而是“换到定位更匹配的”,并且要在动手前把消费位点对齐和历史消息搬运的方案想清楚——否则迁移本身就成了一个新的技术债。
选型的结论最后要落成一段能被后来人挑战的文字:我们选 X,是因为消息是任务还是事件流、吞吐量级是多少、需要或不需要哪些语义、团队对哪套最熟。把理由写下来,比记住“当时谁拍板选了谁”更有价值——三年后有人质疑“当初为什么不用 Kafka”时,这段文字就是答案。
参考资料
如果这篇文章对你有帮助