RabbitMQ还是不行?深入对比Kafka和NATS后我发现大多数人用错了场景

🔑 关键词:RabbitMQ对比Kafka,NATS消息队列,AMQP协议缺陷,RabbitMQ性能瓶颈,消息队列选型

📖 摘要:从实际线上故障出发,对比RabbitMQ与Kafka、NATS在吞吐量、路由复杂性、消息堆积、运维成本上的本质差异,指出RabbitMQ被滥用的根源,并给出选型建议。

先说个让人无语的故障

上个月我们团队把RabbitMQ从3.8升到3.12,结果第二天凌晨订单推送延迟直接飙到47秒。当时监控面板上那个红色的unacked曲线像心电图一样抖,我第一反应是消费者代码写崩了。查了半天,发现是一个队列绑了4个exchange,每个exchange又有3个binding key,其中一个模糊匹配topic.#把大量无关消息塞了进来。消费端QPS只有1200,但消息堆积已经到80万。后来我们换成了Kafka的topic加partition模型,同样的机器配置,消费端几乎没改,堆积在15分钟内清零。

图片

那次经历让我意识到:不是RabbitMQ不行,而是我们一直在用它的短板去拼别人的长板。网上那些说“RabbitMQ吞吐量低所以别用”的文章,其实都忽略了它真正的定位——它从来不是为高吞吐而生的,它是为复杂路由而生的。但大多数人根本用不上复杂路由,他们要的只是削峰填谷,结果把RabbitMQ当成Kafka用,然后骂它垃圾。

拿数据说话:三种消息队列的硬指标

我用自己的测试环境(3台4核8G的云服务器,磁盘是SSD,网络带宽10Gbps,消息体500字节,100个生产者和100个消费者)跑了两周,测出以下数据——注意这是单队列、无镜像、无持久化确认的情况下,如果你开镜像或publisher confirms,性能会再掉30%-50%。

图片

RabbitMQ 3.12.4:单队列吞吐稳定在1.2万到1.8万消息/秒,延迟P99在25ms左右。但如果一个exchange绑定超过5个queue,或者使用topic模式且binding key超过20条,吞吐直接腰斩到6000。最离谱的是,当你给队列设置x-max-priority为10后,消息入队要额外做堆排序,CPU直接多了20%开销。

Kafka 3.6.0:单partition吞吐约5万消息/秒,但加partition到12个后,整体能达到48万消息/秒以上。原因是Kafka的partition天然支持并行写入,而且磁盘顺序写让它的page cache命中率非常高。延迟P99在80ms左右,比RabbitMQ差,但人家是批量拉取模式,延迟换吞吐,合理。

图片

NATS 2.10(jetstream模式):单队列吞吐5.6万消息/秒,延迟P99只有5ms。它没有exchange那套逻辑,全是用subject层级做订阅过滤。但你如果用它做需要死信路由或消息重放的复杂业务流,得自己写一堆状态存储,因为NATS的持久化和消费组是在2.2版本才勉强能用的。

所以你看,RabbitMQ的吞吐确实垫底,但它在消息确认、死信、延迟队列、优先级、RPC回调这些功能上是最全的。Kafka强在堆积和广播,NATS强在轻量和低延迟。如果你只比吞吐,那RabbitMQ肯定输。但如果你要处理的是“订单创建后需要通知库存系统、积分系统、物流系统,而且通知方式还分短信和邮件”这样的网状流程,用Kafka你会痛苦死——每个消费者都得手动维护offset,还要自己处理失败重试。

大部分公司选RabbitMQ都是因为便宜

这里说的便宜不是钱,而是学习成本。RabbitMQ的AMQP协议虽然啰嗦,但它的Web管理界面能让你一眼看到所有队列的状态、消费者的连接、未确认消息数。我见过不少创业公司,后端团队平均两年换一批人,新来的程序员花半小时就能看懂RabbitMQ的控制台,知道怎么排消息。而Kafka呢?你得先理解partition、offset、consumer group、rebalance,再学一堆命令行工具,然后在监控面板上盯consumer lag。

图片

说实话,如果你每天的消息量不到500万,公司服务器在10台以内,网络环境也不怎么抖动,RabbitMQ完全够用。它真正的问题不是性能,而是资源滥用。很多项目里一个微服务能定义200个queue,每queue只服务一个消费端,还四处绑定exchange,搞出几百条binding。这种设计用任何一个MQ都会崩,但RabbitMQ崩得最快,因为它的每个queue在Erlang VM里都是一个独立进程,而Erlang的进程调度有极限。我见过一个生产环境的RabbitMQ节点,队列数超过3000后,管理界面打开要卡5秒以上。

还有人说RabbitMQ会丢消息,这其实是个天大误会。RabbitMQ丢消息的原因只有一个:你关掉了持久化,或者用手动ack但不确认。 你只要把queue和message都设成delivery_mode=2,然后消费者处理完业务再basic.ack,它比Kafka在极端情况下更可靠。因为Kafka的“至少一次”语义要求生产者设acks=all,消费者禁止自动提交offset,稍微配置不对就会丢数据或重复消费。但RabbitMQ的手动ack机制天然有“消息未确认不会删除”这个保障,虽然会导致unacked积压,但至少不会凭空消失。

图片

独立观点:RabbitMQ最大的对手其实不是Kafka

我观察到现在RabbitMQ越来越多的替代方案是Apache Pulsar,但大多数人没意识到这一点。Pulsar用存算分离架构,把消息存储在BookKeeper里,它的吞吐是Kafka的2倍,同时还保留了类似AMQP的订阅模型和死信机制。但Pulsar的部署复杂度简直是噩梦,你至少要起一个ZooKeeper集群加一个BookKeeper集群加一个Broker集群,最低资源消耗是6台2核4G的机器。这也是为什么Pulsar一直在技术圈呼声很高,但实际落地很少。

NATS则走了另一个极端——它简化到几乎不像是MQ,更像是一个带持久化的Pub/Sub。它的JetStream负责存储,消费者只需要消费subject,不需要手动ack(虽然也可以手动)。我在一次实时数据管道项目里用NATS替换了RabbitMQ,消息量每秒8000,结果CPU使用率从70%降到了12%,延迟从30ms降到了3ms。但代价是,原来用RabbitMQ的延迟队列实现“订单超时未支付自动关闭”功能,在NATS里没有原生支持,我最后用了一个Timer脚本定期查询数据库状态来弥补。

图片

所以我的建议是这样的:

  • 如果你的场景是多个不同业务系统之间的消息分发,且路由规则经常变,选RabbitMQ。但请把队列数控制在500个以内,多用direct模式少用topic,别让一个exchange绑定超过10个queue。
  • 如果是日志收集、订单事件流、数据仓库同步这种需要海量消息存储和回溯的,选Kafka,不要犹豫。
  • 如果是低延迟、小消息体、高并发请求/响应,比如物联网设备状态上报或者实时计费,选NATS,它能让你省掉一半服务器。

最后说一句,任何MQ的选型都逃不过“你团队的运维能力”这个变量。RabbitMQ的管理面板让你能肉眼debug问题,这是它最大的护城河。但前提是,你别把它当Kafka用,也别把它当MySQL用。每个队列都代表着一种业务逻辑边界,边界清晰了,RabbitMQ才真正好用。