Kafka 的架构是怎么样的?
结论:Kafka 是一个"分区 + 副本"的分布式提交日志(Commit Log)。Topic 被切成多个分区(Partition),每个分区有多个副本(Replica),其中一个是 Leader 负责读写,其余 Follower 只做同步;生产者按分区器写入 Leader,消费者以消费组(Consumer Group)为单位分摊分区,位点存在内部 Topic
__consumer_offsets。
一、整体结构
┌───────────────────────────┐
│ 元数据管理(KRaft 或 ZK) │
│ Controller 选主、ISR 变更 │
└────────────┬──────────────┘
│
Producer ──分区器──> ┌─────────▼──────────────────────────────┐
│ Broker 集群 │
│ broker-0 broker-1 broker-2 │
│ P0(Leader) P0(Follower) P0(Follower)│
│ P1(Follower) P1(Leader) P1(Follower)│
│ P2(Follower) P2(Follower) P2(Leader) │
└─────────┬──────────────────────────────┘
│ 每分区只由 Leader 提供读写
┌─────────▼──────────┐
│ Consumer Group A │
│ c1: P0 c2: P1,P2 │ 一个分区只分给一个消费者
└────────────────────┘
二、核心概念
| 概念 | 含义 | 要点 |
|---|---|---|
| Topic | 逻辑消息类别 | 只是一个名字,物理存储落在分区上 |
| Partition | 有序、追加写的日志段 | 分区数决定并行度上限 |
| Replica | 分区的副本 | 副本因子 3 表示 1 Leader + 2 Follower |
| Leader / Follower | 读写主副本 / 同步副本 | 读写只走 Leader,Follower 主动拉取 |
| ISR | 与 Leader 保持同步的副本集合 | 含 Leader,acks=all 只等 ISR |
| Offset | 分区内消息的唯一序号 | 单调递增,不可修改 |
| Consumer Group | 消费组 | 组内分区互斥分配,组间互不影响 |
| Controller | 集群管理节点 | 负责分区 Leader 选举、副本分配 |
三、存储布局
/data/kafka/order-0/ # Topic=order, Partition=0
├── 00000000000000000000.log # 日志段,默认 1GB 切分
├── 00000000000000000000.index # 稀疏索引:offset -> 物理位置
├── 00000000000000000000.timeindex # 时间戳索引:按时间查找
└── leader-epoch-checkpoint # Leader 纪元记录
消息追加写日志段,索引文件每写入 index.interval.bytes(默认 4096 字节)才记一条,所以索引是"稀疏"的,查询时先二分索引再顺序扫描。这也是 Kafka 能用极少内存索引支撑海量消息的原因。
四、关键配置
# server.properties(KRaft 模式)
process.roles=broker,controller
node.id=1
controller.quorum.voters=1@10.0.0.31:9093,2@10.0.0.32:9093,3@10.0.0.33:9093
listeners=PLAINTEXT://10.0.0.31:9092,CONTROLLER://10.0.0.31:9093
log.dirs=/data/kafka
num.partitions=6
default.replication.factor=3
min.insync.replicas=2
# 建 Topic:3 副本、6 分区
kafka-topics.sh --bootstrap-server 10.0.0.31:9092 --create \
--topic order --partitions 6 --replication-factor 3
kafka-topics.sh --bootstrap-server 10.0.0.31:9092 --describe --topic order
五、常见追问
- 为什么读写都走 Leader,Follower 不能分担读? Kafka 的 Follower 拉取是为了同步而不是服务读请求,让 Follower 读会破坏一致性;需要扩展读吞吐就加分区或加消费者。
- KRaft 相比 ZooKeeper 解决了什么? 去掉了外部依赖,元数据变更走 Raft 日志,Controller 切换从"秒级到分钟级"降到亚秒级,集群规模上限也更高。
- 分区数是不是越多越好? 不是。分区多了会增加文件句柄、副本同步线程与 Controller 元数据压力,还会拉长故障恢复时间,一般按目标吞吐与消费者数估算。
__consumer_offsets是什么? 内部 Topic,保存每个消费组对每个分区的提交位点,默认 50 个分区,靠它实现"消费者重启后从上次位置继续"。
小结
- Kafka 的核心抽象是"分区日志 + 副本",Topic 只是逻辑名。
- 单分区内有序、跨分区无序,这个特性决定了顺序消费与并行度的设计。
- 读写只走 Leader,Follower 负责同步与故障接管,ISR 是二者同步关系的度量。
- 位点存在内部 Topic,消费组通过位点实现断点续传与分区独占。