消息中间件选型这件事我前前后后在三家公司经历过完整的落地过程从早期单体应用拆微服务时引入 RabbitMQ到后来日志采集和埋点链路全面转向 Kafka再到最近两年在交易和订单场景里用 RocketMQ 扛核心链路。每次选型都不是拍脑袋决定的而是被业务场景、团队能力、运维成本这几只手一起推着走。网上很多对比文章停留在Kafka 吞吐高、RabbitMQ 延迟低、RocketMQ 事务强这种一句话结论上真到了要落地的时候根本不够用。这篇就把我踩过的坑、做过的压测、翻过的源码笔记整理出来围绕 Kafka、RocketMQ、RabbitMQ 这三个主流消息中间件聊清楚真实选型到底该看哪些维度。1. 选型之前先搞清楚你到底在解决什么问题很多人一上来就问哪个中间件最好这个问题本身就问错了。消息中间件没有绝对的好坏只有匹配不匹配。我在做选型的时候第一步永远是先把业务场景拆开看清楚消息要承担什么职责。1.1 消息中间件承担的四种典型职责从我的经验看消息中间件在系统里通常扮演四种角色不同角色对中间件的要求差异极大。第一种是异步解耦。比如用户下单后要发短信、加积分、更新推荐模型这些动作不需要同步完成扔到消息队列里慢慢消费就行。这种场景对吞吐要求中等对可靠性要求高对延迟不敏感。第二种是削峰填谷。典型的是秒杀场景瞬时几十万请求打进来后端处理能力只有几千 QPS中间件要能扛住这个洪峰把消息堆起来慢慢消费。这种场景对吞吐和堆积能力要求极高。第三种是数据管道。比如埋点日志、binlog 同步、监控指标采集这类场景消息量巨大但单条价值低允许少量丢失追求的是极致吞吐和顺序性。第四种是分布式事务最终一致。比如订单创建后要扣库存、扣优惠券跨服务的数据一致性靠消息来保证。这种场景对消息的可靠投递、事务消息、顺序消费要求极高。把这四种职责想清楚选型的方向就清晰了一大半。我见过太多团队拿着日志采集的需求去选 RabbitMQ结果堆积一上来就崩也见过拿 RabbitMQ 的事务能力去要求 Kafka最后自己在外层硬造了一套补偿逻辑。1.2 三个中间件的设计哲学差异理解设计哲学比记参数重要得多。这三个中间件从诞生之初就是为不同目标设计的。Kafka的基因是日志处理LinkedIn 当年就是为了把海量日志高效地收集起来才做的它。所以 Kafka 的核心设计是分区、顺序写磁盘、零拷贝一切为了吞吐。它的消费模型是拉模式消费者自己维护 offset消息消费后不删除靠时间或大小过期。这个设计决定了 Kafka 天生适合数据管道和流处理。RocketMQ的基因是阿里内部的交易场景要扛双十一的洪峰还要保证订单消息不丢不重。所以它在 Kafka 的基础上做了很多改造支持事务消息、支持消息重试、支持死信队列、支持消息轨迹。它的消费模型也是拉模式但做了长轮询优化兼顾了吞吐和实时性。RabbitMQ的基因是 AMQP 协议的企业级消息总线强调灵活的路由和可靠投递。它的核心是 Exchange 和 Queue 的解耦通过不同的 Exchange 类型direct、topic、fanout、headers实现复杂的路由逻辑。它的消费模型是推模式Broker 主动推给消费者实时性最好但吞吐相对受限。提示设计哲学决定了中间件的舒适区逆着它的基因去用短期能跑通长期一定出问题。比如用 Kafka 做延迟消息用 RabbitMQ 做海量日志都是典型的逆基因使用。1.3 选型决策的五个核心维度我把选型要看的维度归纳成五个每次做决策都会拿这五个维度打分。维度说明权重参考吞吐与延迟峰值 QPS、平均延迟、P99 延迟高可靠性消息不丢、不重、顺序性保证高功能特性事务、延迟、重试、死信、路由中运维成本部署复杂度、监控、扩容、社区中团队熟悉度现有技术栈、学习成本、排错能力高这五个维度里团队熟悉度经常被忽略但它其实非常关键。我见过一个团队选了 RocketMQ结果线上出问题没人看得懂 Dashboard排查一个消息堆积花了两天。选型不是选最强的是选团队能 hold 住的。2. 吞吐与延迟压测数据背后的真相吞吐和延迟是选型时最常被拿出来对比的指标但网上的数据往往脱离场景。我把自己在不同配置下跑过的压测数据整理出来同时说明这些数字背后的影响因素。2.1 三者的吞吐量级差异在同样的硬件条件下3 节点集群每节点 8C16GSSD 盘我用统一的消息体1KB做了压测结果大致如下。中间件单机吞吐消息/秒3 节点集群吞吐延迟P99Kafka50 万 - 100 万200 万5-20msRocketMQ10 万 - 20 万50 万1-10msRabbitMQ2 万 - 5 万10 万1-5ms这个数据是量级参考实际会因消息大小、副本数、刷盘策略、消费模式而大幅波动。Kafka 的吞吐优势来自顺序写磁盘和零拷贝它把消息按分区顺序追加到日志文件消费时直接从页缓存读几乎不涉及随机 IO。RocketMQ 的吞吐比 Kafka 低一个量级主要因为它默认同步刷盘、同步复制牺牲了部分吞吐换可靠性。RabbitMQ 吞吐最低因为它的消息要经过 Exchange 路由、入队、推送给消费者链路更长。但这里有个反直觉的点吞吐不是越高越好。如果你的业务峰值只有 5 万 QPSKafka 的百万吞吐对你毫无意义反而它的运维复杂度会成为负担。我见过一个日活几万的应用选了 Kafka结果运维成本比业务开发成本还高。2.2 延迟的真实表现与影响因素延迟这块RabbitMQ 的推模式确实有优势消息到达 Broker 后几乎立刻推给消费者P99 能压到个位数毫秒。Kafka 和 RocketMQ 都是拉模式消费者要主动去拉所以有一个轮询间隔的延迟。Kafka 早期版本的拉模式延迟比较明显消费者拉不到消息就 sleep 一段时间再拉导致延迟可能到几百毫秒。后来引入了 fetch.min.bytes 和 fetch.max.wait.ms 参数配合长轮询延迟降到了几十毫秒以内。RocketMQ 的长轮询做得更激进Broker 会 hold 住请求直到有消息或超时所以延迟能压到 10ms 以内。实际业务里延迟敏感的场景其实不多。大部分异步解耦场景几百毫秒的延迟完全可接受。真正对延迟敏感的是实时风控、实时推荐这类场景这时候 RabbitMQ 或者 RocketMQ 更合适。2.3 消息堆积能力的对比堆积能力是削峰填谷场景的核心指标也是很多团队踩坑的地方。Kafka 的堆积能力最强因为它把消息顺序写到磁盘堆积几个 T 的消息都不在话下消费时按 offset 顺序读性能几乎不随堆积量下降。我实测过 Kafka 堆积 5000 万条消息消费速度依然稳定。RocketMQ 的堆积能力也不错消息同样落盘但它的 ConsumeQueue 和 CommitLog 分离设计堆积量大时索引文件会占用较多内存。一般堆积千万级消息没问题再往上要注意内存配置。RabbitMQ 的堆积能力是短板。它的消息默认存在内存里堆积量大时会触发换页到磁盘性能急剧下降。我踩过一次坑RabbitMQ 堆积了 200 万条消息内存直接打满Broker 开始拒绝新消息整个链路雪崩。后来查文档才知道RabbitMQ 要堆积必须配置惰性队列lazy queue但即使配了堆积性能也远不如前两者。注意如果你的场景有削峰需求RabbitMQ 一定要慎用或者必须配置惰性队列并做好容量规划。我个人的经验是 RabbitMQ 堆积超过 100 万条就要警惕了。3. 可靠性消息不丢不重的实现细节可靠性是核心交易场景的命门。消息丢了订单状态不一致消息重了用户被重复扣款。这块三个中间件的实现机制差异很大我逐个拆开讲。3.1 消息不丢的三道防线消息从生产者到 Broker 再到消费者任何一个环节都可能丢消息。要保证不丢得在三道防线上都做文章。第一道防线生产者到 Broker。Kafka 通过 acks 参数控制acks0 不等确认可能丢acks1 等 Leader 确认Leader 挂了可能丢acksall 等所有 ISR 副本确认最可靠。RocketMQ 通过发送方式控制同步发送等 Broker 返回 SendResult异步发送靠回调单向发送不管结果。RabbitMQ 通过 Publisher Confirm 机制Broker 收到消息后回一个 ack。第二道防线Broker 持久化。Kafka 靠副本机制消息写入 Leader 后同步到 ISR 副本Leader 挂了从 ISR 选新 Leader。RocketMQ 靠同步刷盘 同步复制消息写入 CommitLog 后刷盘同时同步到 Slave。RabbitMQ 靠镜像队列或 Quorum Queue消息复制到多个节点。第三道防线Broker 到消费者。Kafka 靠手动提交 offset消费完再提交提交前挂了会重复消费但不会丢。RocketMQ 靠消费位点消费成功才更新 offset失败会重试。RabbitMQ 靠手动 ack消费完再 ack没 ack 的消息会重新入队。这三道防线要全部配好消息才真正不丢。我见过很多团队只配了第一道Broker 一挂消息全没了。3.2 消息不重的幂等设计消息不重比不丢更难因为网络抖动、消费者重启、重试机制都会导致重复消费。三个中间件都只能保证 at-least-once至少一次要做到 exactly-once精确一次必须靠业务侧幂等。Kafka 从 0.11 版本开始支持幂等生产者和事务能保证单分区单会话内的 exactly-once但跨分区跨会话还是要业务幂等。RocketMQ 支持事务消息能保证本地事务和消息发送的原子性但消费端幂等还是要自己做。RabbitMQ 没有内置幂等机制完全靠业务侧。业务侧幂等的常见做法有三种。第一种是用唯一 ID 去重每条消息带一个全局唯一 ID消费前先查 Redis 或数据库看是否处理过。第二种是用状态机比如订单状态从待支付到已支付重复消息因为状态不匹配会被忽略。第三种是用数据库唯一索引插入时靠唯一约束兜底。我个人的经验是幂等设计要在业务建模阶段就考虑进去不要等出了问题再补。我做过一个支付回调的场景一开始没做幂等结果网络抖动导致重复回调用户被重复加积分后来加了一张去重表才解决。3.3 顺序消息的实现与代价顺序消息在订单、库存这类场景很重要但实现代价不小。Kafka 只能保证单分区内有序要全局有序只能用一个分区吞吐直接废掉。所以 Kafka 的顺序消息通常是分区内有序业务上把需要有序的消息路由到同一个分区比如用订单 ID 做 key。RocketMQ 支持分区顺序和全局顺序。分区顺序用 MessageQueueSelector 把同一业务 key 的消息发到同一个队列消费端用 MessageListenerOrderly 顺序消费。全局顺序只能用一个队列吞吐受限。RabbitMQ 的顺序保证比较弱多个消费者并发消费同一个队列时无法保证顺序。要保证顺序只能单消费者或者用多个队列加路由。顺序消息的代价是吞吐下降和消费复杂度上升。我的建议是能用业务手段规避顺序依赖就规避比如用状态机让消息乱序到达也能正确处理实在不行再上顺序消息。4. 功能特性事务、延迟、重试的实战差异功能特性是选型时容易被低估的维度。很多团队选型时只看吞吐上线后才发现缺某个功能只能自己在外层硬造造出来的东西还不如中间件自带的稳。4.1 事务消息RocketMQ 的独门武器事务消息是 RocketMQ 的招牌功能专门解决本地事务和消息发送的原子性问题。典型场景是订单创建后要发消息通知库存服务扣减如果订单创建成功但消息发送失败库存就不一致了。RocketMQ 的事务消息流程是这样的生产者先发一条半消息half message到 BrokerBroker 存下来但不投递生产者执行本地事务根据本地事务结果向 Broker 发送 commit 或 rollbackBroker 收到 commit 才投递消息收到 rollback 就删除半消息。如果生产者迟迟没回结果Broker 会回查生产者的本地事务状态。Kafka 从 0.11 开始支持事务但它的语义是跨分区跨会话的原子写主要解决流处理场景的 exactly-once和 RocketMQ 的事务消息不是一回事。Kafka 的事务不能解决本地数据库事务和消息发送的原子性。RabbitMQ 没有事务消息只能靠本地消息表 定时补偿来实现最终一致。这个方案我做过要维护一张消息表定时扫描未确认的消息重发复杂度不低。4.2 延迟消息三个中间件的不同玩法延迟消息在超时取消、定时提醒这类场景很有用。RocketMQ 原生支持延迟消息但早期只支持固定的 18 个延迟级别1s 到 2h不能任意指定延迟时间。RocketMQ 5.0 之后支持任意时间的延迟消息用时间轮实现。这个功能在电商的超时未支付取消订单场景里非常实用。RabbitMQ 通过 TTL 死信队列实现延迟消息。消息设置 TTL过期后进入死信队列消费者监听死信队列。但这个方案有个坑如果队列是 FIFO 的前面的消息没到期会阻塞后面的消息导致延迟不准。要用延迟插件rabbitmq_delayed_message_exchange才能精确延迟。Kafka 原生不支持延迟消息只能自己在外层实现比如消费后判断时间没到就重新发回队列或者用外部定时器。这个实现起来比较别扭。4.3 消息重试与死信队列消息消费失败后的重试机制三个中间件都有但细节不同。RocketMQ 的重试最完善。消费失败后Broker 会把消息发回重试队列%RETRY%ConsumerGroup按延迟级别递增重试默认重试 16 次。重试耗尽后进入死信队列%DLQ%ConsumerGroup可以人工干预。这个机制在交易场景里非常实用临时故障能自动恢复。RabbitMQ 的重试要靠消费者自己实现或者配置死信交换机。消费失败 nack 后消息可以重新入队或进入死信队列。但 RabbitMQ 没有内置的递增重试要自己控制重试次数和间隔。Kafka 没有内置重试机制消费失败通常靠消费者自己处理比如记录失败消息、发到重试 topic、或者阻塞消费。Kafka 的设计哲学是消费者自己负责中间件不管。提示重试机制一定要配死信队列兜底否则重试耗尽的消息就丢了。我在 RocketMQ 上踩过一次坑没配死信队列监控结果一批重试失败的消息在死信队列里躺了一周才发现。5. 运维与监控上线之后才是真正的考验选型时大家关注的是功能上线后折磨人的是运维。这块我踩的坑最多也最有发言权。5.1 部署复杂度对比Kafka 的部署最重。它依赖 ZooKeeper新版本可以用 KRaft 模式去掉 ZK要规划 broker、topic、partition、replica还要考虑磁盘规划、网络规划。我第一次部署 Kafka 集群花了一整天光是调 server.properties 就调了半天。RocketMQ 的部署中等。它由 NameServer 和 Broker 两部分组成NameServer 无状态可以随便部署Broker 分 Master 和 Slave。部署比 Kafka 简单但比 RabbitMQ 复杂。RocketMQ Dashboard 是个加分项可视化做得不错。RabbitMQ 的部署最简单。单机装个 Erlang 再装 RabbitMQ 就行集群靠 Erlang 的分布式能力自动组网。Docker 部署更是几行命令搞定。但这里有个坑很多人 Docker 部署 RabbitMQ 后用 admin 账号登录发现创建不了虚拟主机这是因为默认的 guest 账号只能本地登录admin 账号的权限也要单独配。5.2 监控指标的差异监控是运维的眼睛三个中间件暴露的指标差异很大。Kafka 的核心监控指标是 Lag消费积压、ISR 数量、UnderReplicatedPartitions、请求延迟。Lag 是最重要的它反映消费者跟生产者的差距。排查 Lag 高的原因要看是生产突增还是消费变慢前者要扩容消费者后者要查消费逻辑。Kafka 的监控工具生态很丰富AKHQ、Kafka Eagle、Confluent Control Center 都能用。RocketMQ 的核心指标是消息堆积量、消费 TPS、发送 TPS、Broker 的 CommitLog 写入延迟。RocketMQ Dashboard 能直观看到这些。RocketMQ 还支持接入 Prometheus配合 Grafana 做监控大盘。RabbitMQ 的核心指标是队列长度、消息速率、连接数、内存使用。RabbitMQ 自带管理界面能看到队列堆积、消费者数量、消息进出速率。但它的监控粒度不如前两者细深度排查要靠 rabbitmqctl 命令。5.3 扩容与故障恢复扩容这块Kafka 的 partition 扩容比较麻烦增加 partition 后消息的 key 路由会变可能导致顺序性问题。Broker 扩容相对简单加节点后重新分配 partition 即可。RocketMQ 的队列扩容也类似Topic 的队列数创建后不好改。Broker 扩容要配置主从关系比 Kafka 稍复杂。RabbitMQ 的扩容最简单加节点后队列自动 rebalance镜像队列或手动迁移Quorum Queue。但 RabbitMQ 的集群有个坑网络分区时可能出现脑裂要配好 partition handling 策略。故障恢复方面Kafka 的 Leader 选举靠 ZK 或 KRaft恢复速度快。RocketMQ 的 Master 挂了 Slave 可以接管但需要配置主从切换。RabbitMQ 的镜像队列在主节点挂了后会重新选主Quorum Queue 基于 Raft 协议一致性更好。6. 真实场景的选型决策与踩坑复盘前面讲了这么多维度最后落到实际选型上我把自己经历过的几个典型场景和决策逻辑分享出来。6.1 日志采集与埋点Kafka 是唯一解日志采集和埋点场景我毫不犹豫选 Kafka。原因很简单数据量大每天几十亿条、允许少量丢失、追求吞吐、下游是流处理或数仓。Kafka 的生态也最完善和 Flink、Spark、Logstash 集成都很顺。这个场景我踩过的坑是 topic 设计。一开始每个业务一个 topic结果 topic 数量爆炸Kafka 的元数据管理压力很大。后来改成按业务域划分 topic用消息头区分具体业务topic 数量降下来了运维也简单了。另一个坑是 partition 数量。partition 太少吞吐上不去太多则文件句柄和内存压力大。我的经验是 partition 数量按峰值吞吐除以单 partition 吞吐来算单 partition 吞吐大概 10MB/s留一倍余量。6.2 交易订单RocketMQ 扛核心链路交易订单场景我选 RocketMQ核心原因是事务消息和重试机制。订单创建、库存扣减、优惠券核销这些动作要保证最终一致RocketMQ 的事务消息能省掉很多补偿逻辑。重试机制也能自动处理临时故障。这个场景踩过的坑是消息顺序。订单状态变更消息必须有序否则会出现已发货先于已支付处理的情况。解决方案是用订单 ID 做分区键保证同一订单的消息进同一个队列消费端用顺序消费。另一个坑是消费幂等。RocketMQ 保证 at-least-once重复消费不可避免。我们在消费端加了一张去重表用消息 ID 做唯一索引重复消息插入失败就跳过。6.3 内部系统通知RabbitMQ 够用且轻量内部系统的通知场景比如审批流、告警、任务调度我选 RabbitMQ。这类场景消息量不大但对路由灵活性要求高RabbitMQ 的 Exchange 机制能轻松实现复杂的路由逻辑。而且 RabbitMQ 部署简单小团队维护成本低。这个场景踩过的坑是惰性队列。有一次告警消息突增队列堆积了几十万条RabbitMQ 内存打满。后来把队列改成 lazy 模式消息直接落盘内存压力小了但消费延迟增加了。所以惰性队列要权衡不是所有队列都适合。另一个坑是虚拟主机和权限。Docker 部署 RabbitMQ 后默认的 guest 账号只能本地登录要新建用户并授权。授权时要指定虚拟主机和权限configure、write、read权限配错会导致连不上或创建不了队列。6.4 选型决策的检查清单最后给一个我实际用的选型检查清单按这个清单过一遍基本不会选错。检查项如果答案是推荐峰值吞吐是否超过 10 万 QPS是Kafka是否需要事务消息是RocketMQ是否需要复杂路由是RabbitMQ是否有海量堆积需求是Kafka/RocketMQ团队是否熟悉该中间件否选最简单的是否需要延迟消息是RocketMQ运维人力是否充足否RabbitMQ这个清单不是绝对的实际选型还要结合业务演进。我见过一个团队一开始用 RabbitMQ业务增长后吞吐扛不住迁移到 RocketMQ迁移过程很痛苦。所以选型时要留一点余量考虑未来一两年的业务增长。我个人在实际操作中的体会是选型没有标准答案只有匹配答案。Kafka、RocketMQ、RabbitMQ 都是优秀的中间件关键是把业务场景、团队能力、运维成本这三者对齐。不要盲目追新也不要死守旧技术根据实际情况做决策并且做好监控和预案这才是选型的正确姿势。
