第 1 章

Kafka 不只是消息队列

第1章:Kafka 不只是消息队列

导读:Kafka 为什么能超越传统消息队列,成为现代数据架构的核心组件?

本章核心问题:Kafka 为什么能超越传统消息队列,成为现代数据架构的核心组件?

读完本章你将理解


Level 1 · 你需要知道的(1-3年经验)

Kafka 的核心设计:拉模型 + 追加日志

追加写日志(Append-Only Log)

Kafka 不用队列,用日志(log)。每个 Topic 分为若干个 Partition,每个 Partition 就是一个追加写的日志文件序列。生产者产生的消息被顺序追加到日志末尾,赋予一个单调递增的偏移量(offset)。

Partition 0 的物理文件:
00000000000000000000.log   (segment 文件)
00000000000000000000.index (稀疏索引)
00000000000000000000.timeindex

追加写示意:
offset=0: {"user": "alice", "action": "view"}
offset=1: {"user": "bob",   "action": "click"}
offset=2: {"user": "alice", "action": "purchase"}
...

追加写是 Kafka 高吞吐的物理基础。磁盘的顺序写速度(500-600 MB/s on HDD)远超随机写(约 100 IOPS × 4KB = 0.4 MB/s),与内存随机访问的差距实际上并不大。Kafka 完全依赖操作系统的 Page Cache,不维护自己的应用层缓存,写入路径极度简化:write(fd, buffer, len) 系统调用 → Page Cache → 后台刷盘。

拉模型(Pull Model)

Kafka 消费者主动向 Broker 发起拉取请求(Fetch Request),决定每次拉多少、从哪个 offset 开始拉。Broker 不需要了解消费者的状态,消费者自己管理自己的 offset(提交到 __consumer_offsets Topic)。

拉模型的好处是:

无限重放

因为消息不会被消费确认所删除,Kafka 的数据保留由时间或大小策略控制(log.retention.hourslog.retention.bytes)。这意味着任何消费者都可以在保留窗口内重放任意历史数据,无需告知生产者或 Broker。

这一特性在实践中意义深远:当 Flink 流处理作业发现计算逻辑有误,可以直接重置消费位点,从出错时间点重跑,无需数据补录。

Kafka 的三种角色

角色一:消息队列(替代 RabbitMQ 场景)

当你的业务需要异步解耦、削峰填谷时,Kafka 可以充当消息队列。典型用法:订单服务产生订单事件,库存服务、通知服务各自消费,互不影响。

Kafka 在此场景的优势是高吞吐(单 Partition 可达数十万条/秒),劣势是延迟略高(p99 通常在 5-20ms,而 RabbitMQ 可以做到 sub-millisecond)。

角色二:分布式存储系统

Kafka 可以配置极长甚至无限的数据保留时间(log.retention.ms=-1),此时它退化为一个按时间序列组织的分布式存储系统,适合:

Kafka Streams 的 StateStore 底层就是一个 Compacted Topic,用 Kafka 自身存储流处理的中间状态,优雅地解决了流处理状态的持久化问题。

角色三:流处理引擎

Kafka Streams(嵌入式库)和 ksqlDB(SQL 层)让 Kafka 直接承担流处理计算。不需要独立的 Flink 或 Spark 集群,Kafka 本身就能完成:

// Kafka Streams:统计每分钟每个商品的点击次数
StreamsBuilder builder = new StreamsBuilder();
KStream<String, ClickEvent> clicks = builder.stream("clicks");

clicks
    .groupByKey()
    .windowedBy(TimeWindows.ofSizeWithNoGrace(Duration.ofMinutes(1)))
    .count(Materialized.as("click-counts"))
    .toStream()
    .to("click-counts-output");

这种"存储即计算"的架构消除了 Lambda 架构中批处理和流处理两套代码的维护负担。

四大竞品深度对比

RabbitMQ

协议:AMQP 0-9-1,支持 MQTT、STOMP 插件。
核心模型:Exchange + Binding + Queue,支持 Fanout、Direct、Topic、Headers 四种路由模式,路由逻辑极为灵活。
延迟:sub-millisecond,是四者中最低的。
吞吐:单节点约 20K-50K msg/s,远低于 Kafka。
消费模型:推模型,消费者注册后 Broker 主动推送。
持久化:消息默认不持久化,需显式配置 durable=true + delivery_mode=2
重放:不支持,消息 ACK 后删除。

适合场景:任务队列(背景任务、邮件发送)、复杂路由(根据消息头路由到不同处理器)、低延迟 RPC。

RocketMQ

背景:阿里巴巴内部孵化,为双十一峰值场景打磨,2012 年开源。
特色功能

吞吐:单集群可达数百万 TPS,与 Kafka 相当。
延迟:p99 约 2-5ms,优于 Kafka。
适合场景:金融级可靠性要求、电商秒杀、有事务语义的业务流。

Apache Pulsar

架构:计算存储分离,Broker 无状态(计算层)+ BookKeeper(存储层),两层独立扩容。
核心优势

劣势:运维复杂度高(需维护 ZooKeeper + BookKeeper + Broker 三层),生态不如 Kafka 成熟。
适合场景:多云/多数据中心部署、SaaS 平台、需要极长数据保留的场景。

Redpanda

实现:用 C++ 重写的 Kafka 协议兼容替代品,基于 Seastar 框架(epoll + 用户态线程调度)。
核心差异

劣势:生态附加组件(Kafka Streams、Kafka Connect、ksqlDB)需要通过兼容层使用,部分高级特性有差异。
适合场景:延迟敏感型场景(高频交易、实时游戏)、资源受限环境、希望降低运维复杂度的中小团队。

决策矩阵:如何选型

维度 Kafka RabbitMQ RocketMQ Pulsar Redpanda
吞吐量 ★★★★★ ★★★ ★★★★★ ★★★★ ★★★★★
端到端延迟 ★★★ ★★★★★ ★★★★ ★★★★ ★★★★★
消息重放 ★★★★★ ★★ ★★★★★ ★★★★★
事务消息 ★★★ ★★★ ★★★★★ ★★★ ★★★
多租户 ★★ ★★★ ★★★ ★★★★★ ★★★
运维复杂度 ★★★ ★★ ★★★ ★★★★★ ★★
生态成熟度 ★★★★★ ★★★★ ★★★★ ★★★ ★★★
云原生存储分层 ★★★ ★★★★★ ★★★★

选 Kafka,当你需要:大规模数据管道(>100K msg/s)、流处理(Flink/Spark/Kafka Streams)、事件溯源、CDC、多个消费者组独立消费同一数据流。

选 RabbitMQ,当你需要:复杂路由逻辑、sub-millisecond 延迟、任务队列(工作项分发给多个工作者)、系统规模相对较小。

选 RocketMQ,当你需要:分布式事务语义、延迟消息、金融级可靠性保证、国内生态支持。

选 Pulsar,当你需要:多数据中心部署、SaaS 多租户隔离、超长数据保留且希望控制成本。

选 Redpanda,当你需要:Kafka 语义但追求更低 JVM GC 抖动、资源受限场景、希望简化运维(不想管 KRaft Quorum)。

Kafka 的本质重申

LinkedIn 当年的问题是:如何构建一个统一的、可回放的、高吞吐的数据总线,让全公司所有系统都能以自己的节奏消费同一份数据流?

Kafka 的答案是:不要用队列,用日志。日志是不可变的,追加写的,消费者用 offset 游标自行遍历,Broker 只负责存储和复制,不参与消费状态的管理。

这个设计决策让 Kafka 超越了"消息队列"的范畴,成为现代数据架构的神经系统:既是管道,又是存储,又是流处理基础设施。理解这一点,是理解本书后续所有内容的基础。


Level 2 · 它是怎么运行的(3-5年经验)

本章的内部原理内容已整合到 Level 1 和 Level 3 中,请结合阅读。


Level 3 · 规范怎么定义的(资深)

传统消息队列的设计局限

推模型与队列语义

RabbitMQ、ActiveMQ 等传统消息中间件建立在 AMQP 协议或 JMS 规范之上,采用推模型(push model):Broker 主动将消息推送给消费者。这带来几个根本性的约束。

**Broker 端队列(broker-side queue)**是推模型的核心数据结构。消息被投递到 Broker 之后,Broker 负责追踪哪些消息已经被哪个消费者确认(ACK)。一旦消息被 ACK,它就从队列中删除,彻底消失。这意味着:

推模型还带来一个隐蔽的问题:Broker 必须了解每个消费者的状态(连接状况、处理速度、未确认消息数),这使得 Broker 成为有状态的重型组件,水平扩展极为困难。

有限的重放能力

传统 MQ 的消息生命周期由消费确认驱动,这从设计层面封死了重放能力。当你发现数据管道中的某个 ETL 任务有 Bug,想用修正后的代码重新处理过去 7 天的数据时,传统 MQ 根本无能为力——那些消息早就被删除了。


Level 4 · 边界与陷阱(所有人)

从 LinkedIn 的痛点出发

2010 年,LinkedIn 的工程师面临一个让人头疼的问题:网站每天产生数以亿计的用户行为事件——页面浏览、好友请求、职位点击——这些数据需要实时流入离线分析系统、推荐引擎和监控平台。他们尝试过 ActiveMQ,试过自研的管道,但这些方案在百万级 TPS 面前都相继崩溃。

Jay Kreps、Neha Narkhede 和 Jun Rao 最终决定重新发明这个轮子,但他们的出发点和传统消息队列截然不同。他们问的不是"怎么把消息从 A 送到 B",而是"怎么让任意多个系统在任意时间消费同一份数据流,并且消费完之后数据还在"。

这个问题的答案,就是 Kafka。

理解 Kafka 为什么不只是消息队列,需要先理解它的核心设计哲学:Kafka 是一个分布式提交日志(distributed commit log),消息队列功能只是这个日志系统的一个用途。

本章评分
4.7  / 5  (112 评分)

💬 留言讨论