Kafka 是当代分布式系统里最重要的中间件之一 —— 它既是消息队列,也是事件日志、流处理基础设施、数据管道的核心。但很多人对 Kafka 的认知停留在"Producer 发,Consumer 收"。深入到 Topic / Partition / Offset / Consumer Group / Replica / Controller / ISR 这些概念,以及它们怎么协同工作,大多数工程师就开始模糊。这篇文章把 Kafka 的内部机制讲透,讲清楚它为什么这么快、可靠性怎么保证、什么时候选它而不是其他 MQ。
Kafka 的核心定位
很多人以为 Kafka 是"更快的 RabbitMQ"。实际不是 —— Kafka 是"分布式提交日志",顺带提供 MQ 语义。这个差别决定了它在哪类场景胜出。
Kafka 的本质:一个持久的、可重放的、按顺序的消息日志。消息写入后不立刻删除(看保留策略,几天到永久),消费者可以从任意 offset 开始读 —— 这点是 RabbitMQ 等传统 MQ 没有的核心能力。
核心概念
Topic 与 Partition
Topic 是消息的"主题",Partition 是 Topic 的物理分片。一个 Topic 由多个 Partition 组成,每个 Partition 是一个有序的日志:
Topic: orders
Partition 0: [msg1, msg2, msg3, ...]
Partition 1: [msg4, msg5, msg6, ...]
Partition 2: [msg7, msg8, msg9, ...]
# Partition 内部消息有序,Partition 之间无序
# 同一个 key 永远落到同一个 Partition(默认按 hash 路由)
Offset
每条消息在 Partition 里有一个递增的 offset(64 位整数)。消费者用 offset 标记自己消费到哪里。"消费"在 Kafka 里就是"读取并推进 offset",消息本身不动。
Consumer Group
多个 Consumer 组成 Consumer Group 共同消费一个 Topic。Kafka 保证:一个 Partition 只会被组内一个 Consumer 消费。所以并发度 = Partition 数 = Consumer 数(超过 Partition 的 Consumer 闲置)。
Topic: orders(8 个 Partition)
Consumer Group: order-service
Consumer A 消费 P0, P1
Consumer B 消费 P2, P3
Consumer C 消费 P4, P5
Consumer D 消费 P6, P7
如果加 Consumer E?它没事干(只有 8 个 Partition,4 个 Consumer 已经够分)
要再多并发,需要增 Partition 数
Broker 与 Replica
Broker 是 Kafka 节点。每个 Partition 有多个 Replica 散布在不同 Broker —— 一个是 Leader(读写都走它),其他是 Follower(复制 Leader 的数据)。
ISR(In-Sync Replicas)
当前"跟得上 Leader 进度"的副本集合。Leader 挂掉时,从 ISR 选新 Leader。不在 ISR 里的 Replica 不能当选(因为它数据可能落后,会丢)。这是 Kafka 数据可靠性的关键。
Producer 的工作流程
1. 序列化:Producer 把 ProducerRecord(key, value)序列化成字节
2. 路由:根据 key 选 Partition(默认 murmur2 hash);key=null 时 round-robin
3. 批量:把消息追加到对应 Partition 的内存 batch
4. 发送:batch 满或 linger.ms 到期,发给 Partition 的 Leader Broker
5. Leader 写入:Leader 把消息追加到本地日志
6. 复制:Follower 主动从 Leader 拉,Leader 等待 ISR 中足够多副本 ack
7. ack 回 Producer
acks = 0:Producer 发完不管 -> 性能最高,可能丢
acks = 1:Leader 写完就 ack -> 默认值,Leader 挂可能丢
acks = all:ISR 全部写完才 ack -> 最可靠,慢一些
幂等 Producer
Kafka 0.11+ 支持幂等 Producer:每条消息带 (PID, sequence),Broker 检测重复就跳过。这让"重试"不会导致重复写入。开启:
props.put("enable.idempotence", true);
// 自动隐含 acks=all、retries=Integer.MAX_VALUE、max.in.flight.requests.per.connection=5
事务 Producer
跨多个 Topic Partition 原子写入:
producer.initTransactions();
try {
producer.beginTransaction();
producer.send(new ProducerRecord("orders", order));
producer.send(new ProducerRecord("inventory_changes", change));
producer.commitTransaction();
} catch (Exception e) {
producer.abortTransaction();
}
消费方设 isolation.level=read_committed,只能看到已 commit 的消息,这就让"消费-处理-写入"循环具备 exactly-once 语义。Kafka Streams、Flink 内部用这个做端到端 exactly-once。
Consumer 的工作流程
1. 加入 Group:第一次启动时联系 Coordinator(某个 Broker),申请加入 group
2. Rebalance:Coordinator 触发 Partition 分配,告诉每个 Consumer "你负责哪些 Partition"
3. Fetch:Consumer 从分配到的 Partition Leader 拉消息
4. 处理:用户代码处理消息
5. Commit offset:Consumer 周期性地提交"消费到的 offset",Coordinator 持久化
# Java 风格
Properties props = new Properties();
props.put("bootstrap.servers", "kafka1:9092");
props.put("group.id", "order-service");
props.put("enable.auto.commit", "false"); // 手动提交更安全
props.put("auto.offset.reset", "earliest"); // 没有 offset 时从头开始
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(List.of("orders"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
process(record);
}
consumer.commitSync(); // 处理完一批再提交
}
消费语义
- at-most-once:先提交 offset 再处理。中途崩了消息丢。
- at-least-once(默认):先处理再提交 offset。崩了 offset 没提交,下次重新读。可能重复。
- exactly-once:消费 + 处理 + 写入 + 提交 offset 在同一个事务里。Kafka 事务 + 幂等消费实现。
Rebalance
Consumer 加入 / 退出 / 心跳超时,触发 group 重新分配 Partition。这期间所有 Consumer 暂停消费,影响吞吐。Kafka 2.4+ 的"Cooperative Rebalance"让 rebalance 增量进行 —— 不需要全部停消费,只是慢慢迁移 Partition。生产强烈建议开。
为什么 Kafka 这么快
Kafka 单节点几十万 QPS,集群百万级 QPS。秘密在几个工程设计:
1. 顺序磁盘 IO
Partition 是顺序追加的文件。顺序磁盘 IO 可以达到几百 MB/s(几乎接近内存速度),比随机 IO 快几个数量级。
2. Page Cache + Zero Copy
Kafka 不维护自己的内存缓存,直接利用 Linux Page Cache。读消息时,通过 sendfile 系统调用,数据从磁盘 → 内核 page cache → 网卡,完全不经过用户态:
普通 IO:磁盘 -> 内核缓冲 -> 用户态缓冲 -> socket 缓冲 -> 网卡 (4 次拷贝)
Kafka zero-copy:磁盘 -> 内核缓冲 -> 网卡 (2 次拷贝)
3. 批量 + 压缩
Producer 批量发,Consumer 批量拉,网络包大小高效利用。支持 lz4 / snappy / gzip / zstd 压缩,大幅减少网络。
4. 分区并行
多个 Partition 各自独立读写,天然并行。集群级吞吐 ∝ Partition 总数。
Kafka 的数据可靠性
Kafka 的"不丢消息"靠几层配合:
- Producer 设 acks=all + 幂等 + 重试:确保消息到达 Broker。
- Broker 设 min.insync.replicas=2(或更多):至少 2 个副本 ack 才算成功。
- Broker 设 unclean.leader.election.enable=false:不允许"落后的副本"当选 Leader(否则会丢数据)。
- Consumer 处理完再提交 offset:避免"读到没处理就当处理过了"。
这套配置下,只要任一时刻 ISR >= min.insync.replicas,数据就不丢。代价是可用性:ISR 不够时 Broker 拒绝写入。
KRaft:不再依赖 Zookeeper
Kafka 长期依赖 Zookeeper 做元数据存储(topic 配置、broker 列表、partition 分配)。Kafka 2.8 起开始引入 KRaft(Kafka Raft):用 Raft 协议在 Kafka 自己内部存元数据,完全去掉 Zookeeper。
3.3 KRaft 正式 GA,3.5 起新集群默认推荐 KRaft。优势:
- 少一个组件,运维简单。
- 元数据操作更快(controller 不再要写 zk)。
- 支持更多 partition(zk 模式下几万 partition 已经吃力,KRaft 模式下可以几百万)。
Kafka 与其他 MQ 对比
维度 Kafka RabbitMQ RocketMQ Pulsar
吞吐 百万级 / 节点 万级 十万级 十万级
延迟 毫秒 亚毫秒 毫秒 亚毫秒
持久化 默认 默认 默认 默认
消息重放 强(按 offset) 弱(消费即删) 支持 支持
顺序保证 partition 级别 队列级别 队列级别 分区级别
事务 支持 AMQP 事务,弱 支持 支持
延时消息 不支持(需外部) 支持(plugin) 原生支持 原生支持
广播订阅 consumer group exchange consumer group subscription
适合场景 日志、事件流、流处理 业务消息、RPC 业务消息、金融 兼顾两者
学习曲线 中 低 中 中-高
选型经验
- 需要极高吞吐 + 重放(日志、事件流、CDC):Kafka。
- 需要复杂路由 + 低延迟 + 业务级 MQ:RabbitMQ。
- 需要电商 / 金融业务消息(顺序、事务、延时):RocketMQ。
- 需要多租户 + 计算存储分离:Pulsar。
常见使用陷阱
坑 1:Partition 设少了不能加。 Partition 数决定并发上限,而 Partition 数只能增不能减,而且增的话 hash 路由会变(同一 key 之前 P3,现在可能 P5),破坏顺序。规则:一开始就估足。常用配置:每 broker 几十到几百 Partition。
坑 2:消息体太大。 单条消息 > 1MB 会触发各种限制(message.max.bytes、replica.fetch.max.bytes)。大数据应该存对象存储 + Kafka 传引用。
坑 3:Consumer 处理慢导致 rebalance。 消费者两次 poll 之间超过 max.poll.interval.ms(默认 5 分钟),被认为挂了,触发 rebalance。规则:单批数据要能在该时间内处理完,慢的逻辑改异步。
坑 4:数据倾斜。 某个 key 流量极大,所有消息都到同一 Partition,该 Partition 的 Consumer 累死。检测:监控每个 Partition 的写入速率。缓解:对 hot key 加随机后缀分散到多个 Partition,或者 hot key 走单独 Topic。
坑 5:磁盘空间。 默认保留 7 天,数据量大可能撑爆磁盘。规则:监控 disk usage,设保留策略(时间或大小),开 log compaction(只保留每个 key 的最新值)。
Kafka Streams:轻量流处理
Kafka 自带流处理框架 Kafka Streams,直接作为 Java 库使用,不需要单独的 Flink / Spark 集群:
StreamsBuilder builder = new StreamsBuilder();
builder.<String, Order>stream("orders")
.filter((k, v) -> v.amount > 1000)
.mapValues(o -> enrichWithUser(o))
.to("high-value-orders");
KafkaStreams streams = new KafkaStreams(builder.build(), props);
streams.start();
Kafka Streams 适合"topology 简单 + 状态量小"的流处理场景(实时风控规则、简单聚合、ETL)。复杂任务(大状态、低延迟、SQL 接口)还是用 Flink。
Kafka Connect:数据管道
Kafka Connect 是连接 Kafka 和其他系统的标准框架。常见连接器:
- Source Connector:从源拉数据到 Kafka(MySQL CDC、PostgreSQL、MongoDB、S3、HTTP)。
- Sink Connector:从 Kafka 推到目标(ES、ClickHouse、S3、JDBC)。
Debezium 是最有名的 CDC source connector,实时同步 MySQL binlog 到 Kafka,后续可以扇出到 ES / 数仓 / 缓存。这套"MySQL → Kafka → 多消费者"管道是现代数据栈的核心。
tiered storage(分层存储)
Kafka 3.6+ 引入分层存储:本地磁盘只存最近热数据,冷数据自动迁到 S3 / OSS。这把"消息保留 30 天"的存储成本从昂贵的本地盘换成廉价对象存储,但接口对消费者透明。大型场景几乎必备。
实战调优清单
- num.partitions:Topic 创建时给足,后续不能减。常用 12-50。
- replication.factor:至少 3,关键数据建议 5。
- min.insync.replicas:>=2,配合 acks=all。
- batch.size + linger.ms:Producer 批量发,提升吞吐(代价:延迟略增)。
- compression.type:开启 lz4 或 zstd,网络省一半。
- fetch.min.bytes + fetch.max.wait.ms:Consumer 批量拉,减少空轮询。
- JVM heap:建议 6-8GB,过大反而 GC 压力。Kafka 主要靠 page cache 而不是 heap。
- 磁盘:RAID 0 提速、SSD 比 HDD 强很多;不要用 RAID 5 / 6(随机写慢)。
写在最后
Kafka 是分布式系统设计的"瑞士军刀":消息队列、事件总线、CDC 管道、流处理基础、跨数据中心同步。把它的核心机制(Partition、Offset、ISR、Consumer Group)理解透,你能解决一大类异步、解耦、流式的问题。
给一个工程心得:不要把 Kafka 当传统 MQ 用。它的杀手锏是"消息可重放 + 多消费者独立进度"—— 这让"新业务上线后从历史数据开始消费""消费侧 bug 修了后重新消费"这类需求变得自然。如果你只是要"发消息 → 收消息",RabbitMQ 可能更合适。Kafka 的复杂性是为它的能力服务的,选它前先想清楚要不要这些能力。
—— 别看了 · 2026