RabbitMQ vs Kafka vs Redis Streams:消息队列选型
"消息队列"不是一种东西——RabbitMQ(传统 AMQP,路由灵活)、Kafka(分布式日志,高吞吐流处理)、Redis Streams(轻量流),设计哲学完全不同。本文讲清三者的核心模型、各自擅长的场景,以及"按需求选而非按热度选"。
本文要点
- 主题聚焦:DevOps
- 适合人群:独立开发者、全栈工程师、AI 应用开发者
- 核心价值:快速理解背景、实践路径、常见误区和工程建议
RabbitMQ vs Kafka vs Redis Streams:消息队列选型
每次架构评审提到"加个消息队列",几乎必然出现一个争论:用 Kafka 还是 RabbitMQ?最近几年又多了一个选项:Redis Streams。争到后面往往发现,争的人其实没分清这三者根本不是同一类东西——只是恰好都叫"消息队列"。
写这篇文章的目的不是站队。三者在各自的领域都是一等一的好工具,关键在于:你的问题是不是它们各自擅长解决的那个问题。下面把三者的核心模型讲透,再讲怎么按需求选。
一、先搞清楚:消息队列根本不是一种东西
很多人把这个概念混在一起,但如果你只读一段话,请读这段:
| 系统 | 本质 | 消息生命周期 |
|---|---|---|
| RabbitMQ | 队列 + 交换器(智能路由) | 消费确认后删除 |
| Kafka | 分布式提交日志(append-only log) | 按策略保留,可重复消费 |
| Redis Streams | 内存里的轻量日志流 | 按策略保留(但受内存约束) |
这三句话决定了后面所有差异:
- RabbitMQ 是消息分发器。一条消息送进去,被路由到某个队列,一个消费者拿走、确认,然后这条消息就消失了。它的核心是"把这条消息可靠地交给该收的人"。
- Kafka 是日志。生产者往日志末尾追加,消费者各自维护一个"读到哪里"的游标(offset),可以重播、可以倒退、可以多个消费者各读各的。消息不会因为"被读过"就消失。
- Redis Streams 是介于两者之间的轻量流,数据结构上更像 Kafka(也是 append-only log + 消费者组),但跑在 Redis 内存里,没有 Kafka 那套重量级生态。
理解这个差异之后,下面所有的设计选择(路由、ACK、分区、保留策略)都不是凭空来的,都是从这三个本质里长出来的。
二、RabbitMQ:经典 AMQP,路由的艺术
2.1 核心模型:Exchange + Queue
RabbitMQ 的灵魂是解耦路由逻辑。生产者不直接把消息塞进队列,而是发给 Exchange;Exchange 根据规则把消息投到一个或多个 Queue。中间这个"根据规则"就是 RabbitMQ 最强的能力。
四种 Exchange 类型,记牢就够用了:
- direct:按
routing_key精确匹配。比如routing_key = "error",只有绑定error的队列收到。 - topic:按模式匹配,
routing_key支持通配符。logs.*.error匹配logs.app.error、logs.db.error。这是用得最多的一种。 - fanout:广播。消息原样发给所有绑定的队列,忽略
routing_key。适合"一条事件多个系统都要处理"。 - header:按消息 header 匹配,不依赖
routing_key。实际项目里少见,但偶尔在复杂路由条件下能救命。
举个典型场景:订单系统发一条 order.created 事件,库存服务、积分服务、推送服务、风控服务都要消费。在 RabbitMQ 里就是:一个 topic exchange,每个服务各起一个队列,按需绑定 order.# 或 order.created 这种 key。互不干扰,各自独立 ACK,各自重试。
2.2 ACK、重试、死信队列:可靠投递的护城河
RabbitMQ 对"消息一定要被正确处理"这件事非常认真:
- 手动 ACK:消费者拿到消息后,处理完了才发
basic_ack。处理中进程崩了?没 ACK,消息会被重新投递给其他消费者。 - ** prefetch**:限制单个消费者未 ACK 的消息数,避免一个消费者被压垮、其他消费者闲死。
- 死信队列(DLX):消息被
basic_reject/basic_nack且不重新投递、TTL 过期、队列满,都可以走死信路由到指定 exchange。异常消息集中处理,不会丢。 - 延迟队列:通过 TTL + DLX 实现,过去是经典玩法(现在 RabbitMQ 也有官方的
rabbitmq_delayed_message_exchange插件,更直接)。
这套机制让 RabbitMQ 在任务分发、订单处理、支付回调这类"每条消息都不能丢、都要有结果"的场景里几乎无对手。
2.3 RabbitMQ 的适用场景
- 任务队列:Worker pool、Celery、Sidekiq 这类后台任务
- 事件通知:业务事件 fanout 给多个订阅者
- RPC:reply-to 队列 + correlation_id,做异步 RPC
- 复杂路由:多种消息类型、按规则分发到不同消费者
- 传统微服务通信:需要可靠投递、ACK、重试
不擅长:消息需要被反复消费(重播)、超大吞吐的日志流(百万 TPS 量级)、长时间的事件溯源。
2.4 部署与运维
- Erlang 写的,分布式靠 clustering + 镜像队列(现在推荐 Quorum Queue,基于 Raft,比老的 mirror queue 更可靠)。
- 单机性能几万到十几万 TPS,跨集群会进一步降。
- 运维复杂度中等,CLI(
rabbitmqctl)和 management UI 都很完善。
三、Kafka:分布式提交日志,为高吞吐而生
3.1 核心模型:Topic → Partition → Offset
Kafka 的核心抽象不是队列,是分区日志:
- 一个 Topic 分成 N 个 Partition。
- 每个 Partition 是一个有序的、不可变的、不断追加的日志。
- 消息在 Partition 内有序,跨 Partition 无序。
- 每条消息有一个 monotonically increasing 的 offset。
- 消费者自己维护 offset,想读到哪读到哪,想倒回去重读就倒回去重读。
这个模型的关键后果:消费完全和生产的"删除"解耦。生产者只管往末尾写,消费者各自维护 offset,互不影响。一条消息可以被 N 个不同的消费者组各消费一次,彼此不知道对方存在。
3.2 消费者组:扩展与并行
Consumer Group 是 Kafka 的精髓:
- 同一个 Group 内的消费者均分所有 Partition(一个 Partition 只被组内一个消费者消费)。
- 想提高消费并行度?加 Partition、加消费者。但消费者数 > Partition 数时,多出来的消费者是废的。
- 不同 Group 之间互不影响,各自独立消费完整数据。
这套机制让 Kafka 天生适合"一份事件流,被 N 个下游系统各自消费"——也就是事件驱动架构 / 数据中台的核心需求。
3.3 消息保留:这才是 Kafka 真正的杀手锏
RabbitMQ 是消费即删除,Kafka 是默认按时间保留 7 天(可配置),消费者有没有消费都无所谓。
这个差异看似简单,实际上彻底改变了你能做的事:
- 事件回放:上线了一个新的消费者,从一周前的 offset 开始消费,把历史数据全量灌进来重新计算。这在 RabbitMQ 里做不到。
- 故障恢复:消费者挂了 3 小时后恢复,从上次 commit 的 offset 接着读,没有数据丢失。
- 事件溯源(Event Sourcing):把所有状态变更作为事件存下来,任何时候可以重建当前状态。
- 流处理:Kafka + Streams / Flink / Spark Streaming,把消息当成流来计算窗口聚合、Join。
- 数据管道(ETL):Kafka Connect 把数据库 binlog、日志、指标全灌进 Kafka,下游随便消费。
3.4 性能与可靠性
- 顺序磁盘写 + 零拷贝(sendfile),单机轻松几十万到上百万 TPS。
- 副本(Replica)+ ISR(In-Sync Replicas)机制保证持久性。
- 通过 idempotent producer + 事务 可以做到 exactly-once 语义(成本是性能)。
- 顺序保证只在 Partition 内,要全局有序就只有 1 个 Partition(吞吐就受限),这是 Kafka 一个固有的权衡。
3.5 Kafka 的适用场景
- 日志聚合:应用日志、访问日志、审计日志
- 事件流 / 流处理:实时数仓、用户行为分析、风控
- 数据管道(ETL):CDC、数据同步
- 事件溯源 / CQRS:需要回放、需要重建状态
- 高吞吐消息总线:微服务之间的事件骨干
不擅长:复杂的路由规则(Kafka 没有 exchange 这种东西,路由靠 Topic 命名约定)、需要严格每条消息可靠投递 + 重试 + 死信的任务队列(要做的话非常繁琐)、小项目(运维成本不划算)。
3.6 部署与运维
- 重。Zookeeper / KRaft、Broker、多副本、监控(Burrow、Kafka Exporter)、Schema Registry、Connect……一套下来就是一个团队。
- 现在 KRaft 模式(移除 Zookeeper)让部署简化不少,但仍然是企业级重量。
- 云上的 Managed Kafka(MSK、Confluent Cloud、Upstash)能省很多运维负担。
四、Redis Streams:藏在 Redis 里的轻量流
4.1 它是什么
Redis 5.0 引入的 Streams 数据结构,本质上是一个内存里的 append-only log + 消费者组模型。API 上很像 Kafka:
XADD写入一条消息,自动生成<timestamp>-<seq>形式的 ID。XREAD/XREADGROUP消费。XGROUP CREATE建消费者组。XPENDING/XCLAIM处理游离消息(某个消费者挂了,它没 ACK 的消息被接管)。XACK确认。XLEN、XRANGE、XREVRANGE随机读取、回放。MAXLEN/MINID限制流长度,做 Trim。
看出来了吗?这就是一个迷你 Kafka——同样的 log + consumer group 模型,只是跑在 Redis 内存里。
4.2 它和 Kafka 的关键差异
| 维度 | Kafka | Redis Streams |
|---|---|---|
| 存储 | 磁盘日志 | Redis 内存(可 RDB/AOF 持久化) |
| 单条大小 | 适合 KB 级别 | 受限于内存,慎放大消息 |
| 吞吐 | 百万级 TPS | 单机几十万 TPS(受内存带宽限制) |
| 保留 | 默认按时间/大小长期保留 | 受内存约束,必须 Trim |
| 集群扩展 | 原生分区 + 副本 | Redis Cluster 分片( Streams 不能跨 slot) |
| 运维成本 | 重 | 极低(如果已有 Redis) |
4.3 它和 RabbitMQ 的关键差异
Redis Streams 没有 exchange / routing。如果你需要按规则把消息投到不同队列,RabbitMQ 是更好的选择。Redis Streams 的"路由"基本只能靠"不同 stream 名"来手动模拟。
4.4 Redis Streams 的适用场景
- 中小项目的事件流:用户在线状态、IM 消息、实时计数
- 跨进程的 Pub/Sub + 消息留存:Redis 原生的 Pub/Sub 断线即丢,Streams 解决了这个问题
- 轻量任务队列:不想再起一个 RabbitMQ 的话,Streams + 消费者组能搞定
- 缓存预热 / 实时排行榜这种 Redis 重度用户:复用已有 Redis,省运维
不擅长:大数据量、需要长期保留的事件流(内存放不下);对持久性要求极高、绝对不能丢的场景(Redis 默认异步持久化,崩溃可能丢最近几秒);复杂的路由规则。
4.5 一个常被忽略的点:Streams ≠ Redis Pub/Sub
Redis 早期的 Pub/Sub 是"在线才收"——订阅者没连着就丢消息,消息也不留存。Streams 的出现很大程度上是为了解决这个痛点。如果你的项目还在用 Pub/Sub 做关键事件分发,该换 Streams 了。
五、横向对比:一张表看清差异
| 维度 | RabbitMQ | Kafka | Redis Streams |
|---|---|---|---|
| 核心模型 | 队列 + 交换器 | 分布式提交日志 | 内存日志流 |
| 消费后 | 删除(ACK 后) | 保留,可重放 | 保留,可重放(需 Trim) |
| 路由能力 | 极强(4 种 exchange) | 弱(靠 Topic 命名) | 无(靠 Stream 命名) |
| 顺序保证 | 单队列内有序 | 单 Partition 内有序 | 单 Stream 内有序 |
| 吞吐 | 数万 ~ 十几万 TPS | 百万级 TPS | 数十万 TPS |
| 延迟 | 低(毫秒级) | 中(依赖 batch) | 极低 |
| 消息保留 | 不保留 | 长(默认 7 天,可永久) | 短(受内存约束) |
| 消费者扩展 | 加队列、加消费者 | 加 Partition、加消费者 | 加消费者(单 Stream 无 Partition) |
| 可靠投递 | 强(ACK + DLX) | 强(副本 + ISR) | 中(取决于持久化配置) |
| 重放能力 | 无 | 强 | 强 |
| 运维复杂度 | 中 | 高 | 低(已有 Redis 时几乎为 0) |
| 学习曲线 | 中 | 较陡 | 平 |
| 典型场景 | 任务队列、RPC、事件通知 | 日志、流处理、ETL、事件溯源 | 轻量事件流、复用 Redis 的项目 |
六、选型决策:按需求,不按热度
把上面所有内容浓缩成一个决策树:
1. 你的需求是"任务分发" / "可靠投递" / "复杂路由" 吗?
比如:订单处理、邮件发送、支付回调、Worker pool、跨服务 RPC、一条事件要按规则分给不同消费者。
→ 选 RabbitMQ。它的 exchange、ACK、DLX、prefetch 都是为这种场景生的。Kafka 也能做,但会非常别扭(你得自己实现重试、死信、延迟队列)。
2. 你的需求是"高吞吐事件流" / "日志" / "需要重放" 吗?
比如:用户行为日志、监控指标、风控事件流、实时数仓、CDC 数据管道、事件溯源、多个下游各自消费一份完整数据。
→ 选 Kafka。这是它无可替代的领域。重放、多消费者组、百万级吞吐、长期保留,是 RabbitMQ / Redis Streams 给不了的。
3. 你的需求是"轻量事件流" / "项目已经在用 Redis" / "不想引入新中间件" 吗?
比如:IM 消息、在线状态、中小型应用的实时通知、用 Redis 重度缓存的 Web 应用。
→ 选 Redis Streams。一个数据结构搞定消息流,运维成本几乎为零。
4. 你都不确定? 那就先回答一个问题:你需要重放消息吗?
- 需要重放 → 在 Kafka 和 Redis Streams 之间选(按规模和已有基础设施)
- 不需要重放,每条消息处理完就该消失 → RabbitMQ
七、别为了"用 Kafka"而上 Kafka
这一节单独拿出来,因为我见过太多团队踩这个坑。
Kafka 在技术圈的名气很大,以至于很多时候变成了一种"政治正确"——一上微服务、一上事件驱动架构,第一反应就是"上 Kafka"。但 Kafka 的成本是非常真实的:
- 运维成本:一套 Kafka 集群(含 Zookeeper / KRaft、监控、Connect、Schema Registry)的运维负担,相当于多养半个中间件团队。
- 复杂度:Partition 数怎么定?副本数怎么定?保留多久?消费者 rebalance 怎么处理?Offset 怎么 commit?这些都要搞清楚,不然线上一定会出事。
- 小吞吐下的性价比低:如果你的场景是几千 TPS,RabbitMQ 一台机器就够了,Kafka 集群反而是过度设计。
我见过一个团队,把订单状态变更这种典型的任务分发场景(每条消息都要可靠投递、要重试、要死信)硬塞进 Kafka,结果为了实现"消费失败重试 + 死信"绕了一大圈(用 retry topic + dead letter topic 模式),代码极其啰嗦,bug 不断。如果一开始就用 RabbitMQ,半天的事。
反过来我也见过:一个日志聚合场景,每天 TB 级数据,团队用 RabbitMQ 跑了半年,集群天天抖,最后迁到 Kafka 一片坦途。
两边的坑都源于同一个错误:选型的时候看的是工具的"名气",而不是自己的"问题"。
所以最后一句话:
选消息队列,先想清楚你要解决的是什么问题。任务分发找 RabbitMQ,高吞吐事件流找 Kafka,轻量场景用 Redis Streams。别让工具的名气替你做架构决策。
小结
-
三者本质不同:RabbitMQ 是"队列+交换器"做消息分发(消费即删),Kafka 是"分布式提交日志"做事件流(保留可重放),Redis Streams 是"内存轻量流"(迷你版 Kafka)。它们的差异是设计哲学层面的,不是配置层面的。
-
选型的核心问题:你要重放吗? 需要重放、多消费者组各自消费完整流 → Kafka 或 Redis Streams。不需要重放、要可靠投递和复杂路由 → RabbitMQ。
-
RabbitMQ 的护城河是路由和可靠投递:四种 exchange、ACK、死信、prefetch。任务队列、RPC、事件通知这种场景,它至今没有对手。
-
Kafka 的护城河是高吞吐 + 重放:百万级 TPS、按时间保留、多消费者组、Partition 并行。日志、流处理、ETL、事件溯源这种场景,无可替代。
-
Redis Streams 是"够用且省事"的选择:已经有 Redis、规模不大、需要轻量事件流——选它,省一个中间件。
金句:选消息队列,看的是你的问题长什么样,不是看哪一个在简历上写出来好看。Kafka 不是 RabbitMQ 的升级版,它们是两种东西;Redis Streams 是第三个选项,不是 RabbitMQ 的廉价替代品。
Related
相关推荐
文章
Docker 部署 Next.js 社区项目的注意事项
围绕 DevOps、Next.js 的技术趋势、工程实践和独立开发视角。
文章
OpenTelemetry 如何帮助你观察 AI 应用?
围绕 DevOps、AI 的技术趋势、工程实践和独立开发视角。
文章
长运行工具调用:Operation、Poll 与 Cancel 的终态协议
初始请求超时后,工具可能仍在服务端运行;发出取消也不等于副作用已停止。本文为单次长运行工具调用建立 operation 身份、轮询水位、取消竞态和唯一终态接受点。
文章
Agent 记忆删除传播:从用户遗忘请求到可验证清除
删除主表一行不会清掉向量、摘要、缓存和恢复后的备份。本文用删除状态机、代际墓碑、派生血缘与端到端 canary 验证真正遗忘。