一、先看全局架构
text
┌─────────────────────────────────────────────┐
│ Kafka Cluster │
│ │
┌──────────┐ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ ┌──────────┐
│ Producer │────▶│ │ Broker 1 │ │ Broker 2 │ │ Broker 3 │ │────▶│ Consumer │
└──────────┘ │ │ │ │ │ │ │ │ └──────────┘
│ │ P0(Leader)│ │ P1(Leader)│ │ P2(Leader)│ │ ┌──────────┐
┌──────────┐ │ │ P1(Follower)│ │ P2(Follower)│ │ P0(Follower)│ │────▶│ Consumer │
│ Producer │────▶│ │ │ │ │ │ │ │ └──────────┘
└──────────┘ │ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ZooKeeper / KRaft (元数据) │
└─────────────────────────────────────────────┘核心角色:
下面逐个拆解,最后串起来。
二、Broker:Kafka 的物理载体
2.1 什么是 Broker
Broker 就是一台运行 Kafka 服务的机器(或容器)。多个 Broker 组成一个 Kafka Cluster(集群)。
每个 Broker 有唯一的 broker.id,在 server.properties 中配置:
properties
broker.id=1
listeners=PLAINTEXT://localhost:9092
log.dirs=/var/kafka-logs2.2 Broker 的职责
存储消息:把 Producer 发来的消息写入磁盘。
提供读写:响应 Producer 的写入和 Consumer 的拉取。
副本管理:维护 Partition 的 Leader/Follower 关系,参与 ISR 同步。
元数据管理:向 ZooKeeper(或 KRaft)注册自身状态。
2.3 集群中的角色分工
Controller:集群中会选出一个 Broker 作为 Controller,负责分区 Leader 选举、副本分配等管理操作。
普通 Broker:负责数据读写。
Leader / Follower:针对每个 Partition 而言,不是针对 Broker。
⚠️ 一个 Broker 可以同时是某些 Partition 的 Leader,又是另一些 Partition 的 Follower。Leader/Follower 是分区的属性,不是 Broker 的属性。
三、Topic 与 Partition:逻辑与物理的分层
3.1 Topic 是逻辑概念
Producer 发送时指定 Topic 名称,Consumer 订阅时也指定 Topic 名称。Topic 本身不存储数据,只是一个逻辑分类。
text
Topic: order-topic
├── Partition 0
├── Partition 1
└── Partition 23.2 Partition 是物理分片
Partition 才是真正存储消息的地方。每个 Partition 是一个有序、不可变的追加日志(Append-only Log)。
text
Partition 0:
offset: 0 1 2 3 4 5 → 时间方向
┌────┬────┬────┬────┬────┬────┐
│msg0│msg1│msg2│msg3│msg4│msg5│
└────┴────┴────┴────┴────┴────┘
旧 ────────────────────────▶ 新每条消息在 Partition 内有一个唯一的 offset(从 0 递增)。
offset 只在单个 Partition 内唯一,不同 Partition 的 offset 互不相关。
消息写入后不可修改,只能追加。
3.3 为什么分区
水平扩展:单机存储和吞吐有限,分区可分散到多个 Broker。
并行读写:不同分区可并行读写,提高吞吐。
负载均衡:分区可均匀分配到各 Broker。
顺序保证:分区内有序,是 Kafka 顺序性的最小单位。
3.4 Partition 与 Broker 的映射
假设 3 个 Broker、3 个分区、副本数 2:
text
Broker 1: P0(Leader) P1(Follower) P2(Follower)
Broker 2: P0(Follower) P1(Leader) P2(Follower)
Broker 3: P0(Follower) P1(Follower) P2(Leader)每个 Partition 有一个 Leader 和若干 Follower,Leader 和 Follower 分布在不同 Broker 上,保证高可用。
四、Replica、Leader、Follower、ISR:高可用的基石
4.1 Replica(副本)
每个 Partition 可以有多个副本,副本数由 replication.factor 决定。
Leader Replica:唯一,负责处理所有读写请求。
Follower Replica:多个,只负责同步 Leader 的数据,不处理客户端请求。
4.2 读写规则
Producer 只写 Leader。
Consumer 只读 Leader(Kafka 2.4+ 支持从 Follower 读,但默认仍是 Leader)。
Follower 只做同步,定期向 Leader 发送 fetch 请求拉取最新数据。
4.3 ISR(In-Sync Replicas)
ISR 是与 Leader 保持同步的副本集合(包括 Leader 自己)。
text
Partition 0:
Leader: Broker 1 ┐
Follower: Broker 2 ├── ISR = [1, 2, 3]
Follower: Broker 3 ┘如果 Broker 3 落后太多或宕机,会被踢出 ISR:
text
ISR = [1, 2]4.4 acks 与 ISR 的关系
Producer 的 acks 参数决定写入确认级别:
配合 min.insync.replicas(ISR 最小副本数),可以保证强可靠性。
4.5 Leader 选举
当 Leader 所在 Broker 宕机,Controller 会从 ISR 中选出一个新 Leader。
默认优先选 ISR 中的副本,保证数据不丢。
如果 ISR 为空(极端情况),可配置
unclean.leader.election.enable=true允许非 ISR 副本当 Leader,但会丢数据。
五、Segment:Partition 下的物理文件
很多人把 Partition 和 Segment 搞混。Partition 是逻辑分片,Segment 是 Partition 下的物理文件分段。
5.1 为什么需要 Segment
如果一个 Partition 只有一个巨大文件:
删除旧消息困难。
文件过大,查找慢。
不利于日志清理和压缩。
所以 Kafka 把 Partition 切分成多个 Segment:
text
Partition 0 (目录: order-topic-0/)
├── 00000000000000000000.log ← Segment 1 (offset 0~999)
├── 00000000000000000000.index ← 索引文件
├── 00000000000000001000.log ← Segment 2 (offset 1000~1999)
├── 00000000000000001000.index
├── 00000000000000002000.log ← 当前活跃 Segment
└── 00000000000000002000.index文件名是该 Segment 第一条消息的 offset。
只有最后一个 Segment 是"活跃"的,可写入。
每个 Segment 默认 1GB(
log.segment.bytes),写满后滚动创建新 Segment。
5.2 索引文件
.index 是稀疏索引,记录 offset 到物理位置的映射,加速查找:
text
offset 0 → 文件位置 0
offset 100 → 文件位置 4096
offset 200 → 文件位置 8192Consumer 要找 offset=150 的消息:先在 index 中定位到 offset=100 的位置,再从该位置顺序扫描到 150。
5.3 日志清理
Kafka 有两种清理策略:
注意:Kafka 的删除以 Segment 为单位,不会删除 Segment 中间的消息。
5.4 各概念层级关系
text
Kafka Cluster
└── Broker (物理机器)
└── Topic (逻辑分类)
└── Partition (逻辑分片,分布在不同 Broker)
└── Replica (副本,Leader + Follower)
└── Segment (物理文件分段)
└── Message (单条消息,有 offset)六、Producer:消息如何被发送
6.1 发送流程
text
Producer
│
│ 1. 序列化 key/value
▼
Serializer
│
│ 2. 分区器决定 Partition
▼
Partitioner ── hash(key) % partitions ──▶ 选定 Partition
│
│ 3. 写入 RecordAccumulator(按分区缓冲)
▼
RecordAccumulator (32MB 默认)
│
│ 4. Sender 线程批量拉取
▼
NetworkClient
│
│ 5. 发送到 Partition 的 Leader Broker
▼
Broker (Leader)
│
│ 6. 写入 Leader,同步到 ISR Follower
▼
返回 ack6.2 关键组件
6.3 分区选择逻辑
java
// 无 key:粘性分区(Kafka 2.4+)
if (key == null) {
// 先集中发一个分区,攒批后切换
}
// 有 key:hash 取模
else {
partition = murmurHash2(key) % numPartitions;
}6.4 批量与重试
批量:
batch.size(默认 16KB)+linger.ms(默认 0),攒批提高吞吐。重试:
retries(默认 Integer.MAX_VALUE),失败自动重试。幂等:
enable.idempotence=true保证重试不产生重复消息。
七、Consumer 与 Consumer Group:消息如何被消费
7.1 Consumer 是拉模式
Consumer 主动向 Broker 发送 fetch 请求拉取消息,不是 Broker 推送。
为什么用拉模式:
Consumer 按自身处理能力拉取,避免被压垮。
可批量拉取,提高吞吐。
可自主控制 offset。
7.2 Consumer Group(消费者组)
Consumer Group 由 group.id 标识。groupId 是 Kafka 消费语义的核心。
7.3 分区分配规则
核心规则:同一个 Consumer Group 内,一个 Partition 只能被一个 Consumer 消费。
text
Topic: order-topic (3 分区)
Group: order-group
场景 1: 1 个 Consumer
Consumer A ← P0, P1, P2
场景 2: 2 个 Consumer
Consumer A ← P0, P1
Consumer B ← P2
场景 3: 3 个 Consumer
Consumer A ← P0
Consumer B ← P1
Consumer C ← P2
场景 4: 4 个 Consumer
Consumer A ← P0
Consumer B ← P1
Consumer C ← P2
Consumer D ← (空闲)结论:同组内有效消费者数 ≤ 分区数,多出的消费者会空闲。
7.4 分区分配策略
7.5 Rebalance(再均衡)
触发条件:
消费者加入组(新实例启动)。
消费者离开组(宕机或主动退出)。
订阅的 Topic 分区数变化。
Rebalance 过程:
text
1. 所有消费者停止消费(Stop-The-World)
2. 重新选举 Group Coordinator
3. 按策略重新分配分区
4. 各消费者从新分配的分区继续消费问题:Rebalance 期间整个组暂停消费,频繁 Rebalance 严重影响吞吐。
优化:
调大
session.timeout.ms和max.poll.interval.ms。使用
CooperativeStickyAssignor。避免消费者处理时间过长。
静态成员(
group.instance.id)减少重启触发的 Rebalance。
7.6 Offset 管理
Offset 是 Consumer 在 Partition 中的消费位置。
text
Partition 0:
┌────┬────┬────┬────┬────┬────┐
│msg0│msg1│msg2│msg3│msg4│msg5│
└────┴────┴────┴────┴────┴────┘
▲
│
current offset = 3
(下一条要消费的是 msg3)Offset 提交方式:
手动提交模式:
Offset 存储位置:
旧版本:ZooKeeper
新版本:内部 Topic
__consumer_offsets(默认 50 个分区)
7.7 Consumer 拉取流程
text
Consumer
│
│ 1. 加入 Group,触发 Rebalance,分配分区
▼
Group Coordinator
│
│ 2. 返回分配的分区列表
▼
Consumer
│
│ 3. 向分区 Leader 发送 FetchRequest
▼
Broker (Leader)
│
│ 4. 返回消息 + 当前 offset
▼
Consumer
│
│ 5. 处理消息
▼
│ 6. 提交 offset 到 __consumer_offsets
▼
Group Coordinator八、一条消息的完整生命周期
把上面所有概念串起来,看一条消息从生产到消费的全过程。
8.1 场景设定
集群:3 个 Broker(B1、B2、B3)
Topic:
order-topic,3 个分区,副本数 2分区分布:
text
B1: P0(Leader), P1(Follower) B2: P1(Leader), P2(Follower) B3: P2(Leader), P0(Follower)生产者:
KafkaTemplate<String, String>消费者:两个实例,groupId 都是
order-group
8.2 生产阶段
text
1. 业务代码调用:
kafkaTemplate.send("order-topic", "order-1001", "订单已创建");
2. Producer 序列化 key/value:
key = "order-1001"
value = "订单已创建"
3. Partitioner 计算分区:
hash("order-1001") % 3 = 1
→ 目标 Partition = P1
4. Producer 查询元数据,找到 P1 的 Leader:
P1 Leader = B2
5. 消息写入 RecordAccumulator 中 P1 的缓冲区
6. Sender 线程批量发送到 B2
7. B2 写入 P1 Leader 日志:
P1: offset=0 的消息 = "订单已创建"
8. B2 同步给 P1 的 Follower(B1)
9. ISR 全部确认后,返回 ack 给 Producer8.3 存储阶段
text
P1 在 B2 上的物理文件:
/var/kafka-logs/order-topic-1/
├── 00000000000000000000.log
│ └── offset=0: key=order-1001, value=订单已创建
└── 00000000000000000000.index
└── 0 → 文件位置 08.4 消费阶段
text
1. 两个 Consumer 实例(C1、C2)启动,groupId=order-group
2. 向 Group Coordinator 注册,触发 Rebalance
3. 分区分配:
C1 ← P0, P1
C2 ← P2
4. C1 向 P1 的 Leader(B2)发送 FetchRequest:
fetch(partition=P1, offset=0)
5. B2 返回消息:
offset=0, key=order-1001, value=订单已创建
6. C1 处理消息,业务逻辑执行成功
7. C1 提交 offset=1 到 __consumer_offsets:
group=order-group, topic=order-topic,
partition=P1, offset=1
8. 下一条消息到来时,C1 从 offset=1 继续拉取8.5 完整链路图
text
┌──────────┐
│ 业务代码 │
└────┬─────┘
│ send(topic, key, value)
▼
┌──────────────┐
│ Serializer │
└────┬─────────┘
│ bytes
▼
┌──────────────┐
│ Partitioner │ hash(key) % 3 = 1
└────┬─────────┘
│ partition=1
▼
┌──────────────────┐
│ RecordAccumulator │ 按分区缓冲
└────┬─────────────┘
│ 批量
▼
┌──────────────┐
│ Sender Thread │
└────┬─────────┘
│ 发到 P1 Leader (B2)
▼
┌──────────────────────────────────┐
│ Broker 2 │
│ ┌────────────────────────────┐ │
│ │ P1 (Leader) │ │
│ │ offset=0: 订单已创建 │ │
│ └────────────┬───────────────┘ │
│ │ 同步 │
│ ┌────────────▼───────────────┐ │
│ │ P1 (Follower on B1) │ │
│ └────────────────────────────┘ │
└──────────────────────────────────┘
▲
│ FetchRequest
│
┌────┴─────┐
│ Consumer │ group=order-group
│ C1 │ 分配 P0, P1
└────┬─────┘
│ 处理成功
▼
┌──────────────────────┐
│ __consumer_offsets │
│ order-group / P1=1 │
└──────────────────────┘九、各组件协同关系总结
9.1 关系图
text
┌─────────────┐
│ Producer │
└──────┬──────┘
│ 发送
▼
┌─────────────┐
│ Topic │ 逻辑分类
└──────┬──────┘
│ 分片
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Partition0│ │Partition1│ │Partition2│ 逻辑分片
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
│ 副本 │ 副本 │ 副本
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Leader/ │ │Leader/ │ │Leader/ │ 物理副本
│Follower │ │Follower │ │Follower │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
│ 存储在 │ 存储在 │ 存储在
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Segment │ │Segment │ │Segment │ 物理文件
│.log/.index│ │.log/.index│ │.log/.index│
└──────────┘ └──────────┘ └──────────┘
▲ ▲ ▲
│ │ │
└────────────┼────────────┘
│ 拉取
┌─────┴──────┐
│ Consumer │
│ (Group) │ groupId 决定消费模式
└────────────┘9.2 关键协同规则
9.3 常见问题速答
Q1:一个 Partition 能被多个 Consumer 消费吗?
同组内:不能,只能被一个 Consumer 消费。
不同组:可以,每个组各自消费一份。
Q2:分区数能增加吗?
能增加,不能减少。增加后
hash(key) % partitions可能改变,破坏 key 顺序。
Q3:ISR 是什么?
与 Leader 保持同步的副本集合。
acks=all时,ISR 全部确认才返回。
Q4:Segment 和 Partition 什么关系?
Partition 是逻辑分片,Segment 是 Partition 下的物理文件分段。
Q5:Consumer 数量超过分区数会怎样?
多出的 Consumer 空闲,浪费资源。
Q6:Rebalance 会丢消息吗?
不会丢消息,但会暂停消费。未提交的 offset 会被重新消费,可能重复。
十、性能与可靠性设计要点
10.1 生产者侧
10.2 消费者侧
10.3 Broker 侧
十一、一句话总结
Kafka 的内部工作原理可以概括为:
Producer 按 key 的 hash 决定消息进入哪个 Partition,Partition 分布在多个 Broker 上,每个 Partition 有 Leader/Follower 副本保证高可用,消息以 Segment 文件形式持久化;Consumer 以 groupId 标识消费者组,同组内分区独占、竞争消费,不同组广播;Consumer 通过提交 Offset 记录消费进度,Rebalance 在消费者变化时重新分配分区。
理解这条链路,就理解了 Kafka 的核心。所有配置、调优、问题排查,最终都围绕这条链路展开。