跳转到内容

Kafka 核心架构


单个 Topic 如果只有一个分区,所有消息都写入同一个文件,吞吐量受单机 I/O 限制,消费也只能单线程串行处理。引入 Partition 后:

  • 写入:多个 Partition 分布在不同 Broker 上,并行写入,突破单机瓶颈
  • 消费:Consumer Group 内多个消费者可以各自负责不同 Partition,并行消费
  • 扩展:增加 Partition 数量即可线性提升吞吐量
Topic: order-events (3个分区,分布在2个Broker上)
Broker 1 Broker 2
├── Partition 0 (Leader) ├── Partition 1 (Leader)
└── Partition 2 (Follower) └── Partition 0 (Follower)
└── Partition 2 (Leader) → Partition 1 (Follower)

Producer 发送消息时,按以下规则决定目标分区:

情况分配策略
指定了 Partition直接写入该分区
指定了 Keyhash(key) % partitionCount 计算,相同 Key 始终进同一分区
既没有 Key 也没有指定分区轮询(Round-robin)或粘性分配(Sticky)

Kafka 只保证单个 Partition 内的消息有序,跨 Partition 不保证顺序。

Partition 0: [订单A-创建] [订单A-支付] [订单A-发货] ← 有序 ✅
Partition 1: [订单B-创建] [订单B-支付] ← 有序 ✅
跨分区混合消费 ← 无序 ❌

Offset 是消息在 Partition 中的唯一递增序号,从 0 开始,每写入一条消息加 1。它同时承担两个职责:

  • 消息定位:通过 Offset 可以精确找到 Partition 中的任意一条消息。
  • 消费进度记录:消费者提交当前已消费到的 Offset,重启后从该位置继续。
Partition 0 物理存储:
┌────────────────────────────────────────────────┐
│ offset=0 │ offset=1 │ offset=2 │ offset=3 │ ...│
│ msg_A │ msg_B │ msg_C │ msg_D │ │
└────────────────────────────────────────────────┘
Consumer 已提交 offset=2
下次从 offset=3 开始消费

Kafka 将消费者提交的 Offset 存储在内置的特殊 Topic __consumer_offsets 中,以 Consumer Group + Topic + Partition 为 Key 记录。

由于消息写入后不会立即删除(默认保留 7 天),消费者可以通过重置 Offset 实现消息回溯:

// 在 @KafkaListener 中指定从头消费
@KafkaListener(topics = "order-events", groupId = "order-group")
// application.yml 中配置首次消费策略
// auto-offset-reset: earliest(从最早)/ latest(从最新)

Consumer Group 内,每个 Partition 只能被一个消费者消费,但一个消费者可以消费多个 Partition。

场景一:消费者数 = 分区数(理想状态)
Partition 0 ──▶ Consumer 1
Partition 1 ──▶ Consumer 2
Partition 2 ──▶ Consumer 3
场景二:消费者数 < 分区数(一个消费者负责多个分区)
Partition 0 ──▶ Consumer 1
Partition 1 ──▶ Consumer 1
Partition 2 ──▶ Consumer 2
场景三:消费者数 > 分区数(有消费者空闲)
Partition 0 ──▶ Consumer 1
Partition 1 ──▶ Consumer 2
✗ Consumer 3(闲置,无法分配到分区)

不同 Consumer Group 之间完全独立,各自维护自己的 Offset,同一条消息可以被多个消费者组分别消费:

Topic: order-events
├──▶ Consumer Group A(订单服务)── 各自独立消费,互不影响
└──▶ Consumer Group B(统计服务)── 各自独立消费,互不影响

这是 Kafka 天然支持广播的方式,与 RabbitMQ Fanout Exchange 实现广播的机制完全不同。

Kafka 提供三种分区分配策略,通过 partition.assignment.strategy 配置:

策略说明适用场景
RangeAssignor(默认)按分区范围分配,可能导致分配不均分区数能被消费者数整除时
RoundRobinAssignor轮询分配,尽量均衡消费者订阅相同 Topic 时
StickyAssignor尽量保持上次分配结果,减少 Rebalance 影响生产环境推荐

每个 Partition 可以配置多个副本(replication-factor),分布在不同 Broker 上,提供高可用保障。

副本分两种角色:

  • Leader 副本:负责处理该 Partition 的所有读写请求。
  • Follower 副本:只负责从 Leader 同步数据,不对外提供服务。
Partition 0 的副本分布(replication-factor=3):
Broker 1: Partition 0 Leader ← 处理读写
Broker 2: Partition 0 Follower ← 同步数据
Broker 3: Partition 0 Follower ← 同步数据

ISR 是与 Leader 保持同步的副本集合,包含 Leader 本身。只有 ISR 中的副本才有资格在 Leader 宕机时被选为新的 Leader。

ISR = {Leader, Follower1, Follower2} ← 正常状态
若 Follower2 同步太慢(落后超过阈值),被踢出 ISR:
ISR = {Leader, Follower1}

Kafka 集群中有一个特殊的 Broker 担任 Controller 角色,负责:

  • 监控各 Broker 的存活状态
  • 在 Leader 副本宕机时触发新 Leader 选举
  • 管理 Partition 和副本的元数据

KRaft 模式下,Controller 的元数据直接由 Kafka 自身的 Raft 协议维护,不再依赖 Zookeeper。

┌─────────────────────────────────────────────────────┐
│ Kafka Cluster │
│ │
│ Broker 1 Broker 2 Broker 3│
│ ┌──────────────┐ ┌──────────────┐ ┌─────┐│
│ │P0-Leader │ │P1-Leader │ │P2-L ││
│ │P1-Follower │ │P2-Follower │ │P0-F ││
│ │P2-Follower │ │P0-Follower │ │P1-F ││
│ └──────────────┘ └──────────────┘ └─────┘│
└─────────────────────────────────────────────────────┘
↑ ↑ ↓ ↓
Producer A Producer B Consumer Group A
├── Consumer 1 (P0)
├── Consumer 2 (P1)
└── Consumer 3 (P2)
组件职责
Broker存储 Partition 数据,处理读写请求
Controller管理集群元数据,负责 Leader 选举
Leader 副本处理该 Partition 的读写
Follower 副本同步 Leader 数据,Leader 宕机时接替
ISR保持同步的副本集合,Leader 选举候选池