为什么要用消息队列?Kafka、RabbitMQ、RocketMQ 怎么选?

进阶高频对比约 6 分钟读完

一句话回答

消息队列的三个核心作用是解耦(生产者不需要知道谁在消费)、异步(非关键步骤不阻塞主流程)、削峰(突发流量先进队列,消费者按自己的能力处理)。代价是系统更复杂,要处理消息丢失、重复、顺序和积压,还多了一个要保证高可用的组件。选型上:Kafka 是分区日志模型,吞吐高、消息可重放,适合日志、埋点和流处理;RabbitMQ 用交换机灵活路由,功能丰富、延迟低,适合业务消息;RocketMQ 的事务消息、延时消息、重试和死信开箱即用,适合交易类业务。

详细解析

三个核心作用

文本
同步调用:下单 ──> 扣库存 ──> 发优惠券 ──> 发短信 ──> 加积分 ──> 返回
          任何一个下游慢或挂了,下单就慢或失败;新增下游要改下单代码

引入 MQ:下单 ──> 扣库存 ──> 发"订单已创建"消息 ──> 返回
                                   │
                    ┌──────────────┼──────────────┐
                 优惠券服务      短信服务       积分服务(各自订阅、各自重试)
  • 解耦:新增一个下游只需要订阅主题,不用改上游代码
  • 异步:用户只等核心步骤,响应更快
  • 削峰:秒杀时请求先写入队列,后端按固定速度消费,数据库不会被瞬间打垮

引入的问题

问题 说明 详见
可用性 MQ 挂了,依赖它的链路都受影响,MQ 本身要集群部署
消息丢失 生产、存储、消费任何一个环节都可能丢 消息不丢失
重复消费 至少一次投递必然带来重复 重复消费与幂等
顺序与积压 多分区、多消费者天然乱序;消费慢会积压 顺序与积压
一致性 上游成功、下游失败,数据只能最终一致 分布式事务

所以不是越多越好:需要立即拿到结果的调用(比如查询库存)不适合走 MQ。

三者的模型差异

Kafka:主题分成多个分区,每个分区是一个只追加的日志文件,消息按 offset 顺序存储,按保留时间或大小删除,而不是消费后删除。消费者拉取消息,自己记录消费到哪个 offset,所以可以回退 offset 重新消费。同一个消费者组里,一个分区只分配给一个消费者。

RabbitMQ:实现 AMQP 0-9-1。生产者把消息发给交换机,交换机按类型(direct、topic、fanout、headers)和绑定规则把消息路由到队列,Broker 推送给消费者,消息被 ack 后从队列删除。路由灵活,支持优先级队列、TTL、死信交换机等,新版本也提供了可重放的 Stream 类型。

RocketMQ:同样是主题 + 队列的日志模型,消费者组机制和 Kafka 类似。内置事务消息(半消息 + 回查)、延时/定时消息、消费失败自动重试和死信队列、Broker 端按 Tag 或 SQL92 属性过滤,这些都是业务开发常用的功能。它的 Push 消费者底层也是长轮询拉取,只是 SDK 封装成了推的形式。

选型对比

维度 Kafka RabbitMQ RocketMQ
存储模型 分区日志,按保留策略删除 队列,ack 后删除 日志 + 消费队列
消费方式 拉 推(也支持拉) 拉和推都有封装
吞吐 很高,批量和顺序写磁盘做得极致 中等 高
路由能力 只按主题和分区 最灵活 Tag 或 SQL92 属性过滤
消息重放 支持(重置 offset) 普通队列不支持,Stream 支持 支持(按时间重置位点)
事务消息 有事务,但语义是跨分区原子写 无(AMQP 信道事务不是这个语义) 有,专为本地事务 + 发消息设计
延时消息 无原生支持 TTL + 死信,或插件 原生支持(早期版本只有固定的延时级别)
生态 流处理生态最完整(Kafka Streams、Flink 等) 多语言客户端成熟 Java 生态最成熟

选型建议:

  • 日志采集、埋点、CDC、流式计算:Kafka
  • 业务消息、复杂路由、对延迟敏感、规模中等:RabbitMQ
  • 电商交易、需要事务消息和延时消息:RocketMQ
  • 云上项目也可以直接用托管服务,少一份运维成本

"Kafka 吞吐最高"是架构决定的(顺序写、批量、零拷贝,见 零拷贝),具体数字取决于消息大小、副本数、确认级别和硬件,不要背网上的数字,要在自己的环境里压测。

面试官可能追问

Kafka 为什么快?

主要有几点:消息顺序追加写磁盘,顺序写速度远高于随机写;读写大量利用操作系统的页缓存;消费时通过 sendfile 零拷贝把数据从页缓存直接发到网卡;生产和消费都按批处理,并支持批量压缩;主题分成多个分区,可以在多台 Broker 上并行读写。

RabbitMQ 怎么实现延时消息?

两种方式。一是给消息或队列设置 TTL,不设消费者,消息过期后通过死信交换机转到真正的处理队列,缺点是队列只检查队头消息是否过期,不同 TTL 的消息放在同一个队列会互相阻塞;二是延时消息交换机插件(rabbitmq-delayed-message-exchange),它的限制不少:待投递的消息只存在一个节点上,这个节点丢了消息就丢了;不支持 mandatory;不适合几十万以上的大量延时消息,官方现在也已停止维护它,新项目要先查当前版本有没有原生的替代方案。大量、长时间的延时任务也可以用数据库或 Redis 有序集合加定时扫描来做。

什么时候不该用消息队列?

调用方需要同步拿到结果时;业务量小、链路简单,多一个组件带来的运维和排障成本大于收益时;强一致要求的操作(比如扣款后立即要看到余额)也不适合拆成异步。另外,Node.js 服务内部的后台任务可以先考虑基于 Redis 的任务队列,见 任务队列。

易错点

  • 把消息队列当万能的解耦工具,所有调用都改成异步,排查问题和保证一致性都会变难
  • 认为 Kafka 消费完消息就删除,实际是按保留策略删除,与是否消费无关
  • 背诵具体的吞吐量数字,这些数字和测试条件强相关
  • 混淆 Kafka 的事务和 RocketMQ 的事务消息,两者解决的问题不同

AI 模拟面试官

用自己的话回答,AI 对照参考答案打分、指出遗漏,再追问,最多 3 轮

登录后就可以和 AI 面试官对练,面试记录也会保存下来。登录

这道题你掌握了吗?

选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。

学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。