读书笔记 · 约 14,537 字 · 预计推送 2 部分

主题精读:事件驱动系统与消息队列——Kafka、RabbitMQ、NATS、Redis Streams 的适用边界 未开始

目录

题记与说明


一、核心问题:这份工作在解决什么问题

四个系统都落在"发布/订阅 + 消息传递"这个大前提之下,但它们是四种不同的架构哲学,而不是同一工具的四个版本:

系统本质模型一句话定位
Apache Kafka分布式提交日志(append-only immutable log + 分区 + offset)事件流平台:记录、重放、流式分析
RabbitMQ智能代理/哑消费者的消息队列(AMQP:exchange → queue → binding)通用消息代理:复杂路由、任务分发、per-message 确认
NATS(core / JetStream)轻量连接层(subject 树发布订阅;JetStream 加持久化流)服务间低延迟通信 + 边缘/IoT 消息织网
Redis StreamsRedis 内的内存日志流(XADD/XREADGROUP/XACK,consumer group)复用已有 Redis 的轻量事件管道

本主题要回答的核心问题:当需求特征(吞吐、延迟、持久化、重放、路由复杂度、团队运维能力、已有技术栈)不同时,应该选哪一个——各自的适用边界在哪里,越界使用会发生什么。 正如 CloudAMQP 开篇所批评的,多数讨论把问题问错了方向:

"If you're asking yourself if Apache Kafka is better than RabbitMQ or if RabbitMQ is more reliable than Apache Kafka, I want to stop you right there. … the decision of whether to go with RabbitMQ or Apache Kafka is dependent on the requirements of your project, and a true comparison can only be made if you have used them both with the correct setup in a fitting scenario."
(如果你在问"Kafka 是不是比 RabbitMQ 好",我要先打断你……选型取决于你项目的需求;只有在合适场景、正确配置下两者都用过,比较才有意义。)
— CloudAMQP, *When to use RabbitMQ or Apache Kafka*

二、主要主张:各家的适用边界

2.1 总主张:没有最好的队列,只有匹配需求驱动的队列

五个来源殊途同归给出同一结论:选型要先写出"架构驱动"(architectural drivers),再对着需求打分,而不是看社区声量。dev.to 的作者在真实项目中就是这么做的,他列出的四个驱动是:持久化与重放需求、运维复杂度、性能、学习曲线——并加上一个最容易被忽略的第五项:它是否已经在你的技术栈里。

2.2 Kafka 的边界:为"记录与重放"而生

CloudAMQP 对两者的经典定位(原文引述,出自其长期置顶的 StackOverflow 回答):

"RabbitMQ is a solid, mature, general-purpose message broker that supports several protocols such as AMQP, MQTT, STOMP, and more. RabbitMQ can handle high throughput. A common use case for it is to handle background jobs or to act as a message broker between microservices. Kafka is a message bus optimized for high-ingress data streams and replay. Kafka can be seen as a durable message broker where applications can process and re-process streamed data on disk."
(Kafka 是为高入口数据流和重放优化的消息总线:一个持久化的 broker,应用可以在磁盘上反复处理流数据。)

适用边界内:事件溯源、点击流/行为追踪、日志聚合、大数据分析管道、需要重放修复 bug 的流处理、审计要求。边界外/代价:不支持消息优先级;路由模型极简("Kafka does not support routing"——路由要靠多 topic + consumer group 或 Kafka Streams 自己搭);运维概念密度高(topic/partition/offset/consumer group/KRaft),CloudAMQP 坦言"Kafka 出故障后的修复过程更耗时、更混乱"。

2.3 RabbitMQ 的边界:为"路由与任务"而生

RabbitMQ 的杀手锏是四种 exchange(direct/topic/fanout/headers)+ 优先级队列 + per-message ack/nack + 死信队列 + 多协议(AMQP 0.9.1 原生,MQTT/STOMP 插件):

"Chief among RabbitMQ's benefits is the ability to flexibly route messages. Direct or regular expression-based routing allows messages to reach specific queues without additional code."
(RabbitMQ 的首要优势是灵活路由:direct 或正则路由让消息无需额外代码就能到达特定队列。)

适用边界内:长耗时后台任务(生成 PDF、发邮件)、微服务间的命令/事件总线、需要优先级插队、需要逐条确认与重投递的场景。边界外/代价:传统队列(classic/quorum)消费即删除,没有重放语义——直到 v3.9 引入 RabbitMQ Streams(append-only log,可重读)。CloudAMQP 特别指出:传统队列"空时最快",积压数百万条会耗尽内存,扩容方向是加 consumer(竞争消费)而非加节点——"Kafka is built from the ground up with horizontal scaling in mind, while RabbitMQ is mostly designed for vertical scaling"。

2.4 NATS 的边界:为"低延迟通信与轻运维"而生

AutoMQ 的对比给出核心反差:Kafka 的运维栈(ZooKeeper/KRaft + Connect + Streams + MirrorMaker)在 NATS 里很多是"开箱即有":

"Even if you take the zookeepers out of the picture, NATS remains much 'lighter' to run than Kafka. There are a lot of things that NATS does 'out of the box' for which with Kafka you would have to deploy Kafka Streams, connectors, and mirror maker or Replicator nodes on top of your broker nodes."
(就算把 ZooKeeper 拿掉,NATS 运行起来仍然比 Kafka "轻"得多。NATS 开箱即有的很多能力,在 Kafka 里你要在 broker 之上额外部署 Streams、connector、MirrorMaker。)

NATS 是双层结构:core NATS 是无持久化的 at-most-once fire-and-forget;JetStream 补上持久化(内存或文件存储、RAFT 复制、按时间/序号/数量重放)。官方文档进一步把 JetStream 的保留策略分为三种,其设计问题是"谁决定一条消息已经处理完":limits(按上限保留,接近日志模型)、interest/workqueue(消费者 ack 后即删,接近队列模型)——即 JetStream 可按流配置成"日志"或"队列"两种形态(https://docs.nats.io/learn/jetstream/retention-policies 、https://docs.nats.io/learn/jetstream/policies ,经检索摘要确认)。适用边界内:微服务间低延迟 RPC/pub-sub(原生 request-response 模型)、IoT/边缘计算(单二进制 <15MB、Go 实现、资源占用小)、多租户(account 级隔离——"Kafka does not natively support multi-tenancy")。边界外/代价:生态与客户端库成熟度弱于 Kafka(AutoMQ 引社区反馈:"Kafka and the client libraries seems to be much more stable. We had and still have issues on NATS Jetstream with Consumers");consumer group 概念对新手更绕("The concept of consumer group in Kafka is much cleaner than the same in NATS")。

2.5 Redis Streams 的边界:为"已有 Redis + 中等规模"而生

dev.to 复盘的决策表格(原文引述):

DriverKafkaRedis Streams
persistence & replayability持久化到廉价磁盘,可从头重放持久化到昂贵的 RAM,长流可行但更贵
operational complexityHighLow,或栈里已有 Redis 时 Very Low
performance最高吞吐、中等延迟高吞吐、低延迟
learning curve陡峭平缓,与 Redis 其他场景同一套 API

结论原文:

"Choose Redis Streams if you: Already have Redis in your stack / Need low latency and moderate throughput / Want minimal operational complexity. Stick with Kafka if you: Need to process millions of messages per second (for sure) / Need long-term persistence and replay capabilities / Have a dedicated infrastructure team."
(选 Redis Streams:栈里已有 Redis、需要低延迟+中等吞吐、想要最小运维复杂度;坚持用 Kafka:确定要每秒百万级消息、需要长期持久化与重放、有专职 infra 团队。)

边界外的三颗钉子(作者上线一年踩出来的):

  1. 内存上限只能按条数管:Streams 只能 XADD ... MAXLEN ~ 1000 按长度近似裁剪,没有"按内存占用上限"的选项,payload 大小不可控时(如 webhook)要靠压缩(他们 gzip 压缩比约 3)或"流里只传元数据、大块进 S3"的混合方案。
  2. 淘汰策略会整流删除:LRU/LFU/Random 淘汰把整个 stream 当成一个 key——"if you set this policy to LRU, LFU, or Random, and stream is like any other key, it can be removed entirely"(参见 redis/redis#4543)。
  3. 重放与持久化的代价:重放要求保留已处理消息(不能 XDEL),而这全在内存里;重启不丢还得开 RDB/AOF 持久化。

2.6 关键辨析:AWS 页面讲的是 Redis pub/sub,不是 Redis Streams

AWS 对比页的多数否定结论(不保留消息、at-most-once、无法检测重复)针对的是 Redis 的传统 pub/sub,不适用于 Redis Streams(后者有 consumer group、XACK、at-least-once)。精读时必须做这个区分,直接引用 AWS 页面会系统性低估 Redis Streams 的能力,这是本次主题里最容易踩的"来源陷阱"(AWS 页面原文:"Redis OSS doesn't store messages after they are delivered. If no subscribers are connected to the stream, Redis OSS discards the messages"——描述的是 pub/sub 语义)。


三、方法:来源们如何论证

  1. 供应商/托管商对比页(AWS、CloudAMQP、AutoMQ):架构模型解释 + 功能矩阵 + 按用例归档("长任务→RabbitMQ;数据分析→Kafka"),CloudAMQP 还叠加了多年托管服务的客户案例(DoorDash、Softonic、MapQuest、Parkster)。
  2. 基准测试文(Onidel):固定配置(4 vCPU / 8GB RAM / NVMe 的 VPS)上对比吞吐、延迟,再叠加投递语义与最小资源配置的决策矩阵。
  3. 一线工程复盘(dev.to):先列架构驱动打分 → 深入机制(XADD/XGROUP/XACK/XTRIM 命令级演示)→ 上线一年后的"真实世界问题"清单(Redis 挂了怎么办、内存失控、淘汰策略、持久化)——这是五篇里唯一回答"用起来之后哪里疼"的来源。

四、数据与证据

4.1 Onidel 2025 基准(4 vCPU / 8GB / NVMe VPS,声明为优化配置下的结果)

指标KafkaNATS JetStreamRabbitMQ
吞吐500k–1M+ msg/s(batching)200k–400k msg/s(持久化)50k–100k msg/s(durability)
延迟10–50ms(batching+副本开销)亚毫秒(内存)/ 1–5ms(持久化)5–20ms(随队列深度/持久化变化)
最小生产配置8+ vCPU / 16GB RAM2+ vCPU / 4GB RAM4+ vCPU / 8GB RAM
HA 机制分区多副本 + ISR + 自动选主RAFT(R3 副本流)+ 全网状集群自愈quorum queues(RAFT)/ classic 主从
"__Performance Note:__ These benchmarks assume optimized configurations. Real-world performance varies significantly based on message size, persistence requirements, and cluster topology."
(该基准假设优化过的配置;真实性能随消息大小、持久化要求和集群拓扑显著变化。)— Onidel

4.2 投递语义对照(AutoMQ + Onidel 交叉)

语义KafkaNATSRabbitMQRedis Streams
at-most-once✗✓(core NATS)可配置可配置
at-least-once✓(默认)✓(JetStream)✓(publisher confirm + consumer ack)✓(XREADGROUP + XACK)
exactly-once✓(幂等 producer + 事务)JetStream:声明支持(去重窗口;AutoMQ 表述为 ✓)✗✗(需业务侧幂等)

注意:两来源对 JetStream exactly-once 的表述有出入(Onidel 称"exactly once delivery through deduplication based on message IDs";AutoMQ 的表格在 exactly-once 行也标了 JetStream ✓,但正文口径接近"强 at-least-once + 去重窗口")。NATS 官方文档检索到的要点只确认了三种保留策略与 ack/redelivery 机制,未直接背书 exactly-once——实际工程中应按"去重窗口内的有效一次性"理解,并以 docs.nats.io 原文为准。

4.3 其他关键证据点


五、结论:适用边界决策框架

5.1 一句话版

要记录、要重放、要吞吐 → Kafka;要路由、要任务、要逐条确认 → RabbitMQ;要低延迟服务间通信、要边缘轻部署 → NATS;已有 Redis、要轻量管道 → Redis Streams。

5.2 四系统边界矩阵

维度KafkaRabbitMQNATS / JetStreamRedis Streams
吞吐量级最高(磁盘顺序写+batching)中高中高(受内存限)
延迟中(10–50ms 级)中低(5–20ms)最低(亚毫秒级)低(内存读写)
持久化磁盘为主,廉价、可分层传统队列消费即删;Streams 补日志模型JetStream 内存/文件可选内存(昂贵),靠 RDB/AOF 兜底
重放原生(offset + compaction)仅 Streams 队列JetStream 按时间/序号/数量有但费内存,且重放与清理互斥
路由无(靠 topic 规划/Streams 自建)最强(4 种 exchange + 优先级)subject 通配树无
扩展模型水平扩展(分区/节点)垂直扩展为主 + 加 consumer集群自动均衡、全 mesh受单实例/集群内存约束
运维复杂度高(概念多、生态组件多)中(管理 UI 友好)低(单二进制 <15MB)极低(若 Redis 已在栈中)
甜区场景事件溯源、日志聚合、流分析管道长任务、工作流编排、复杂投递规则微服务通信、IoT/边缘、多租户MVP、中等规模微服务事件管道

5.3 三条跨来源一致的高级结论

  1. "消费即删"与"日志式保留"是根本不同的交付模型:前者(RabbitMQ 传统队列)天然适合任务分发;后者(Kafka/RabbitMQ Streams/JetStream)才支撑事件溯源、消费者故障后重算、调试回放。选型第一步是判断你要哪类模型。
  2. 边界在融合,甜区在资源模型:RabbitMQ Streams 和 NATS JetStream 都在向 Kafka 的日志模型靠拢,功能表上的差距在缩小;但持久化的物理介质(Kafka=廉价磁盘、Redis Streams=昂贵内存、JetStream=可配)决定各自的成本甜区,短期不会被抹平。
  3. 混合是常态不是妥协:Kafka 做数据骨干 + NATS/RabbitMQ 做服务间消息,是五篇来源中被多个独立来源证实的企业实践;AutoMQ 明确把它写成推荐选项("using both technologies can provide the best of both worlds")。

六、局限:作者承认的与本人推断的

作者承认的:

本人推断的(来源未明确说、但阅读交叉可见):


七、对我的启发

  1. 先写架构驱动再选型(dev.to 方法,直接可复用):把"持久化与重放需求 / 运维复杂度 / 性能 / 学习曲线 / 是否已在技术栈"五项写下来打分,而不是从"哪个最火"出发。这套驱动清单对任何中间件选型(数据库、缓存、任务队列)都成立。
  2. YAGNI 在中间件上最灵:"The YAGNI principle applies perfectly here. … Redis Streams are also inexpensive to implement, so if you decide to switch to Kafka in the future, you won't have wasted too much work."(dev.to)——从轻量方案起步、留好迁移口,比一步到位押注重型基础设施更稳。
  3. 判断交付模型是第一问:拿到任何"要不要上消息队列"的问题,先问"消费后消息还要不要在?"——不要在(任务)→ RabbitMQ 式队列;要在(事件/审计/重算)→ Kafka 式日志。这一个问题的答案就能砍掉一半候选。
  4. 内存型消息系统必须显式治理内存:Redis Streams 的三条纪律(MAXLEN 近似裁剪、禁用会整流删除的 LRU 淘汰、必要时"流内传元数据+大对象外置 S3")是"能跑"与"能上生产"的分水岭,可当作 checklist。
  5. 对个人自动化/本地管线的启示(衔接已有 n8n、SQLite 精读笔记):本机或小团队规模的事件驱动(抓取→清洗→通知)用 Redis Streams 或 NATS 完全够用;引入 Kafka 只在"多消费组独立消费同一事件流 + 需要重放历史"真实出现时才值得。
  6. 对供应商内容的批判性阅读:AWS 的 pub/sub/Streams 混淆、Onidel 与 AutoMQ 的口径出入提醒我——中间件的"能力结论"必须回溯到官方文档与自己的基准,第三方对比页适合做地图,不适合做裁决。

附:本次精读的来源与检索路径

← 资料库今日播报 →