1. Kafka 基础概念:Topic、Partition、Broker、Producer、Consumer、Consumer Group 的定义与关系
Kafka 是一个分布式、分区化、多副本的发布-订阅消息系统。理解其核心概念是掌握消费者组与分区分配机制的前提。本节将逐一拆解 Topic、Partition、Broker、Producer、Consumer 和 Consumer Group 的定义,并阐明它们之间的协作关系。
1.1 核心概念定义
| 概念 | 定义 | 关键特征 |
|---|---|---|
| Topic(主题) | 消息的逻辑分类名称,是生产者发送消息和消费者订阅消息的基本单位。 | 一个 Topic 可被多个 Consumer Group 独立消费;Topic 内的消息不直接排序。 |
| Partition(分区) | Topic 的物理分片,每个 Partition 是一个有序、不可变的消息日志(append-only log)。 | Partition 内消息按 offset 严格排序;Partition 是 Kafka 并行度的最小单位。 |
| Broker(代理节点) | Kafka 集群中的单个服务器实例,负责存储 Partition 数据、处理读写请求。 | 一个 Broker 可管理多个 Partition;集群通过 Broker 数量实现水平扩展。 |
| Producer(生产者) | 向指定 Topic 发布消息的客户端应用程序。 | 可指定分区策略(如轮询、key 哈希、自定义分区器)。 |
| Consumer(消费者) | 从 Topic 拉取消息的客户端应用程序,通常隶属于一个 Consumer Group。 | 每个 Consumer 负责处理分配给它的 Partition 中的消息。 |
| Consumer Group(消费者组) | 一组 Consumer 实例的逻辑集合,共同消费一个或多个 Topic 的全部消息。 | 组内 Partition 分配策略由 Group Coordinator 协调;组内 Consumer 数量不应超过总 Partition 数。 |
1.2 概念关系与数据流
下图展示了从 Producer 到 Consumer Group 的完整数据流转路径,以及各核心组件之间的层级关系。
1.3 核心关系详解
1.3.1 Topic 与 Partition
- 一对多关系:一个 Topic 被划分为多个 Partition,Partition 数量在创建 Topic 时指定,后续可扩容(但不可缩容)。
- 有序性边界:消息的有序性仅在 Partition 内部保证,跨 Partition 的消息不保证全局顺序。
- 物理存储:每个 Partition 对应 Broker 文件系统上的一个目录,目录内包含多个 segment 文件。
1.3.2 Partition 与 Broker
- 分布存储:Partition 分布在集群的不同 Broker 上,实现负载均衡。
- 主从复制:每个 Partition 有 1 个 Leader 和 N 个 Follower;Leader 负责读写,Follower 同步数据。
- 故障转移:当 Leader Broker 宕机时,Controller 从 ISR(In-Sync Replicas)中选举新的 Leader。
1.3.3 Consumer 与 Partition
- 独占消费:在同一个 Consumer Group 内,一个 Partition 最多只能被一个 Consumer 实例消费。
- 并行度上限:Group 内最大并行消费能力等于 Topic 的总 Partition 数。
- 分配策略:Kafka 提供 RangeAssignor、RoundRobinAssignor、StickyAssignor 等策略,也支持自定义。
💡 关键约束:如果 Consumer Group 内的 Consumer 数量超过 Topic 的 Partition 总数,多余的 Consumer 将处于空闲状态(不接收任何消息)。这是设计消费者组规模时必须避免的陷阱。
1.4 消费者组边界特性
Consumer Group 是 Kafka 实现点对点和发布-订阅两种消息模型的核心机制:
| 特性 | 说明 | 示例场景 |
|---|---|---|
| 组内隔离 | 同一 Group 内的 Consumer 共同消费 Topic 的全部消息,每条消息只被组内一个 Consumer 处理。 | 订单处理服务集群(避免重复处理) |
| 组间独立 | 不同 Group 可以独立消费同一个 Topic,每个 Group 都有自己的消费偏移量(offset)。 | 订单数据同时被实时计算和数仓归档 |
| 动态重平衡 | 当 Group 内 Consumer 加入、离开或 Topic 分区数变化时,触发 Rebalance 重新分配 Partition。 | 服务扩缩容时自动调整消费负载 |
| 偏移量管理 | 每个 Consumer 定期提交已消费的 offset 到 Kafka 内部 Topic(__consumer_offsets)。 | 宕机恢复后从上次提交的 offset 继续消费 |
1.5 生产者与消费者的 Go 语言快速示例
以下代码展示了 Go 语言中使用 segmentio/kafka-go 库创建 Producer 和 Consumer 的基本模式:
// Producer:向 Topic "orders" 发送消息
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/segmentio/kafka-go"
)
func main() {
writer := &kafka.Writer{
Addr: kafka.TCP("localhost:9092"),
Topic: "orders",
Balancer: &kafka.Hash{},
BatchTimeout: 10 * time.Millisecond,
}
defer writer.Close()
err := writer.WriteMessages(context.Background(),
kafka.Message{
Key: []byte("user_123"),
Value: []byte(`{"order_id":"ord_001","amount":99.9}`),
},
)
if err != nil {
log.Fatal("failed to write message:", err)
}
fmt.Println("message sent")
}
// Consumer:从 Consumer Group "order-service" 消费消息
package main
import (
"context"
"fmt"
"log"
"github.com/segmentio/kafka-go"
)
func main() {
reader := kafka.NewReader(kafka.ReaderConfig{
Brokers: []string{"localhost:9092"},
Topic: "orders",
GroupID: "order-service",
MinBytes: 10e3, // 10KB
MaxBytes: 10e6, // 10MB
})
defer reader.Close()
for {
msg, err := reader.ReadMessage(context.Background())
if err != nil {
log.Fatal("failed to read message:", err)
}
fmt.Printf("partition=%d offset=%d key=%s value=%s\n",
msg.Partition, msg.Offset, string(msg.Key), string(msg.Value))
}
}
📌 实践要点:
- Producer 的
Balancer决定消息写入哪个 Partition;kafka.Hash保证相同 Key 的消息进入同一 Partition。 - Consumer 的
GroupID是消费者组的唯一标识;同一 GroupID 的多个 Consumer 实例自动组成一个 Group。 - 建议在 Go 服务启动时通过环境变量注入 GroupID,便于多环境隔离。
1.6 概念关系总结
下图以分层视角总结 Topic、Partition、Broker、Consumer Group 之间的逻辑与物理关系:
⚠️ 常见误解澄清:
- 误解一:Topic 是队列。实际上 Topic 是日志结构,消息被追加后不会删除(除非超过保留时间或大小)。
- 误解二:Consumer Group 内所有 Consumer 必须处理相同的消息。实际上每条消息只被组内一个 Consumer 处理。
- 误解三:Partition 越多越好。实际上过多的 Partition 会增加 Broker 的元数据开销和 Rebalance 时间,建议根据吞吐量合理设置。