Kafka 核心原理
问题
Kafka 的架构是什么样的?为什么 Kafka 吞吐量这么高?Producer 和 Consumer 的工作流程是怎样的?
答案
Kafka 架构总览
核心概念
| 概念 | 说明 |
|---|---|
| Broker | Kafka 服务器节点,负责存储和转发消息 |
| Topic | 消息的逻辑分类,类似数据库的表 |
| Partition | Topic 的物理分片,实现并行读写 |
| Replica | 分区副本,Leader 负责读写,Follower 同步数据 |
| Producer | 消息生产者,将消息发送到 Topic |
| Consumer | 消息消费者,从 Topic 拉取消息 |
| Consumer Group | 消费者组,组内消费者分摊 Partition |
| Offset | 消息在 Partition 中的唯一位置标识 |
| ISR | In-Sync Replicas,与 Leader 保持同步的副本集合 |
| ZooKeeper/KRaft | 元数据管理(Kafka 3.x+ 推荐 KRaft 模式去 ZK) |
Partition 与消息存储
每个 Partition 是一个有序的、不可变的消息序列,消息追加到末尾(append-only)。
Kafka 的磁盘存储结构:
topic-partition/
├── 00000000000000000000.log # 消息日志文件(Segment)
├── 00000000000000000000.index # 稀疏偏移量索引
├── 00000000000000000000.timeindex # 时间戳索引
├── 00000000000005367851.log # 新的 Segment
├── 00000000000005367851.index
└── 00000000000005367851.timeindex
为什么 Kafka 吞吐量高
| 技术 | 说明 |
|---|---|
| 顺序写磁盘 | 追加写入,避免随机 I/O,磁盘顺序写速度接近内存 |
| Page Cache | 利用 OS 页缓存,减少用户态与内核态之间的拷贝 |
| 零拷贝 | sendfile() 系统调用,数据从磁盘直接到网卡,不经过用户空间 |
| 分区并行 | 多 Partition 多 Consumer 并行消费 |
| 批量发送 | Producer 端攒批发送,减少网络请求次数 |
| 压缩 | 支持 gzip/snappy/lz4/zstd 消息压缩 |
零拷贝原理
传统方式:磁盘 → 内核缓冲区 → 用户缓冲区 → Socket 缓冲区 → 网卡(4 次拷贝)
零拷贝(sendfile):磁盘 → 内核缓冲区 → 网卡(2 次拷贝,DMA 直传)
Kafka Consumer 读取消息时就是通过零拷贝实现高效传输。
Producer 发送流程
关键参数:
| 参数 | 说明 | 建议值 |
|---|---|---|
acks | 确认模式:0/1/-1(all) | 高可靠用 all |
retries | 失败重试次数 | 3 或更多 |
batch.size | 批次大小 | 16384(16KB) |
linger.ms | 等待攒批时间 | 5-100ms |
buffer.memory | 缓冲区总大小 | 33554432(32MB) |
max.in.flight.requests.per.connection | 未确认请求数 | 保证顺序设为 1 |
Consumer 消费流程
Consumer 消费示例
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("group.id", "order-group");
props.put("enable.auto.commit", "false"); // 手动提交 offset
props.put("key.deserializer", StringDeserializer.class);
props.put("value.deserializer", StringDeserializer.class);
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("order-topic"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
// 处理消息
processOrder(record.value());
}
// 手动提交 offset,确保消息真正处理完再提交
consumer.commitSync();
}
Consumer Group 与 Rebalance:
- 一个 Partition 只能被同一 Consumer Group 内的一个 Consumer 消费
- Consumer 数量 > Partition 数量时,多余的 Consumer 空闲
- Consumer 加入/离开会触发 Rebalance(重新分配 Partition)
Rebalance 的三种分配策略:
| 策略 | 说明 |
|---|---|
| Range | 按 Topic 的 Partition 范围分配(默认) |
| RoundRobin | 轮询分配所有 Partition |
| Sticky | 尽量保持原有分配不变,减少迁移 |
ISR 机制与副本同步
- ISR(In-Sync Replicas):与 Leader 保持同步的副本列表
- Follower 落后超过
replica.lag.time.max.ms(默认 30s)会被踢出 ISR - Leader 选举只从 ISR 中选取(
unclean.leader.election.enable=false)
常见面试问题
Q1: Kafka 为什么不用内存队列而用磁盘?
答案:
- 顺序写磁盘性能极高:顺序写磁盘速度可达 600MB/s,接近内存的随机写速度
- 利用 Page Cache:OS 自动将磁盘数据缓存到内存,读写效率接近纯内存
- 持久化保障:消息写入磁盘后不怕进程崩溃丢失数据
- 支持消息回溯:消费者可以通过 offset 重新消费历史消息
- 海量堆积能力:磁盘容量远大于内存,可以堆积数十 TB 消息
Q2: Kafka 如何保证消息不丢失?
答案:
需要 Producer、Broker、Consumer 三端配合:
| 端 | 配置 | 说明 |
|---|---|---|
| Producer | acks=all | ISR 全部写入才确认 |
| Producer | retries > 0 | 发送失败重试 |
| Broker | min.insync.replicas=2 | ISR 最少副本数 |
| Broker | unclean.leader.election.enable=false | 禁止非 ISR 副本当选 Leader |
| Consumer | 手动提交 offset | 处理完再提交,避免自动提交丢消息 |
Q3: Kafka 的 acks=0、1、-1 有什么区别?
答案:
- acks=0:Producer 不等待任何确认就返回。最快但可能丢消息
- acks=1:Leader 写入本地 log 就返回确认。Leader 宕机可能丢数据
- acks=-1(all):等待 ISR 中所有副本都写入才返回。最可靠但最慢
生产环境高可靠场景用 acks=all + min.insync.replicas=2。
Q4: Consumer Group 的 Rebalance 是什么?有什么问题?
答案:
Rebalance 是 Consumer Group 内重新分配 Partition 的过程,触发条件:
- Consumer 加入或离开组
- Consumer 心跳超时
- Topic 的 Partition 数变化
问题:Rebalance 期间所有 Consumer 暂停消费(Stop The World),在大量 Partition 时耗时较长。
优化:
- 增大
session.timeout.ms和heartbeat.interval.ms,减少误判 - 使用 Sticky 分配策略,减少不必要的 Partition 迁移
- Kafka 2.4+ 支持 增量 Rebalance(CooperativeStickyAssignor),不再全部暂停
Q5: Kafka 和传统消息队列(RabbitMQ)的本质区别是什么?
答案:
| 维度 | Kafka | RabbitMQ |
|---|---|---|
| 定位 | 分布式流平台 | 传统消息中间件 |
| 存储 | 磁盘持久化,可回溯 | 消费后删除 |
| 消费模式 | Pull(拉取) | Push(推送)+ Pull |
| 吞吐量 | 百万级 TPS | 万级 TPS |
| 消息路由 | Topic + Partition | Exchange + Queue(灵活路由) |
| 消息顺序 | Partition 内有序 | Queue 内有序 |
| 适用场景 | 日志、大数据、事件流 | 业务消息、任务队列 |