3. Topic与Partition

Topic 和 Partition,说白了就是 Kafka 里最核心的两个概念。你建 Topic 就像在数据库里建表,而 Partition 就像是给这张表做了水平分片。我刚开始接触 Kafka 时,总觉得 Partition 越多越好,结果踩了不少坑。今天咱们就把这块彻底聊透。

3.1 Topic 的创建与管理

创建 Topic 的方式有好几种。我个人习惯在测试环境用命令行,生产环境用脚本或自动化工具。先看最直接的方式:

# 创建一个名为 "order-events" 的 Topic,3 个分区,副本因子为 2
bin/kafka-topics.sh --create \
  --bootstrap-server localhost:9092 \
  --topic order-events \
  --partitions 3 \
  --replication-factor 2

这里有几个参数你得注意:

  • --bootstrap-server:指定集群地址,别只写一个 broker,万一挂了就尴尬了
  • --partitions:分区数,决定了你的并行处理能力
  • --replication-factor:副本因子,数据冗余的保障

查看 Topic 列表和详情也很简单:

# 列出所有 Topic
bin/kafka-topics.sh --list --bootstrap-server localhost:9092

# 查看某个 Topic 的详细信息
bin/kafka-topics.sh --describe --topic order-events --bootstrap-server localhost:9092

输出结果会告诉你每个 Partition 的 Leader、Replicas 和 ISR 信息。嗯,这里要提醒一句:生产环境不要随意删除 Topic,除非你确认数据已经备份或不再需要。我曾经见过有人手滑删了核心 Topic,恢复起来那叫一个痛苦。

我的习惯:在创建 Topic 时,我会把分区数和副本因子写进配置管理工具(比如 Ansible 或 Terraform),这样集群重建时能一键恢复,避免遗漏。

3.2 Partition 分区机制:消息到底怎么分?

Partition 是 Kafka 实现高吞吐的关键。每个 Partition 是一个有序的、不可变的消息序列,新消息追加到尾部。你想想看,如果只有一个 Partition,那所有消费者都得排队读,性能肯定上不去。

消息写入哪个 Partition,由分区策略决定。Kafka 默认提供了几种策略:

策略名称 说明 适用场景
RoundRobinPartitioner 轮询分配,消息均匀分布到各分区 无 key 或 key 不重要时
UniformStickyPartitioner 粘性分区,一批消息尽量发到同一个分区 减少网络开销,提升吞吐
自定义 Partitioner 根据业务 key 计算分区 需要保证相同 key 的消息有序

举个例子,如果你处理的是订单数据,希望同一个订单 ID 的所有消息都进入同一个 Partition,那就得用自定义策略:

public class OrderKeyPartitioner implements Partitioner {
    @Override
    public int partition(String topic, Object key, byte[] keyBytes,
                         Object value, byte[] valueBytes, Cluster cluster) {
        // 根据订单 ID 的哈希值取模
        int partitionCount = cluster.partitionCountForTopic(topic);
        return Math.abs(key.hashCode()) % partitionCount;
    }
}

我在项目中遇到过一个问题:某个 Topic 的 Partition 数设得太少,导致部分 Partition 数据量巨大,而其他 Partition 却很空闲。说白了就是数据倾斜。后来我改用自定义 Partitioner,按业务 ID 哈希,才把负载均衡了。

注意:一旦 Topic 创建完成,分区数只能增加不能减少。所以初始分区数要预留一些余量,但也不是越多越好——每个 Partition 对应一个日志文件,太多文件会拖慢 Broker 的启动和恢复速度。

3.3 副本因子设置:数据安全的第一道防线

副本因子(Replication Factor)决定了每个 Partition 有多少个副本。副本因子为 1 表示没有冗余,Broker 挂了数据就丢了。生产环境我建议至少设为 3。

副本之间是怎么工作的?看这张图就明白了:

Partition 副本机制示意图 生产者 Leader 副本 Follower 1 Follower 2 消费者 说明:生产者只写 Leader,Follower 从 Leader 同步数据。消费者也只读 Leader。

副本因子设置时,有几个原则:

  • 至少 2,推荐 3:2 个副本虽然能扛单点故障,但万一再坏一个就危险了
  • 副本数不能超过 Broker 数:比如你有 3 台 Broker,副本因子设为 4 就创建失败
  • 考虑机架感知:如果集群跨机房,尽量让副本分布在不同的机架上
避坑指南:我曾经把副本因子设为 2,结果一台 Broker 宕机后,ISR 只剩一个副本,再写入时风险极高。后来我统一改成 3,配合 min.insync.replicas=2,才算安心。

3.4 分区分配策略:副本该放在哪台 Broker?

当你创建 Topic 时,Kafka 需要决定每个 Partition 的 Leader 和 Follower 分别放在哪些 Broker 上。这个过程叫分区分配。Kafka 默认的策略是 RoundRobin,尽量让 Leader 均匀分布。

但默认策略不一定最优。举个例子,如果你有 3 台 Broker,创建 3 个 Partition 且副本因子为 3,默认分配可能让所有 Leader 都挤在同一台机器上。这就失去了负载均衡的意义。

Kafka 提供了几种分配策略:

策略 原理 优点 缺点
默认 RoundRobin 按 Broker 列表轮询分配 Leader 简单,无需额外配置 可能 Leader 分布不均
Sticky 分配 尽量保持已有分配不变,只调整新增的 减少重分配时的数据移动 实现复杂
自定义分配器 实现 Admin 接口,按机架或标签分配 灵活,适合复杂拓扑 需要开发维护

我建议你在生产环境使用 Sticky 分配。为什么?因为集群扩容或缩容时,默认策略会大量移动 Partition,造成网络和磁盘 IO 飙升。Sticky 策略会尽量保持现有分配,只移动必要的部分。

配置方式很简单,在 server.properties 里加上:

# 使用 Sticky 分配策略
partition.assignment.strategy=org.apache.kafka.clients.consumer.StickyAssignor

另外,还有一个容易被忽略的参数:leader.rebalance.enable。默认是 true,表示 Kafka 会定期检查 Leader 分布是否均匀,如果不均匀就触发重平衡。我个人习惯在稳定集群中把它设为 false,避免不必要的 Leader 切换。

注意:分区分配策略影响的是 消费者组 的分区分配,而不是副本的物理分布。副本的物理分布由 Broker 端的 rack.aware.assignment.strategy 控制。别搞混了。

3.5 实战建议:Topic 设计的最佳实践

聊了这么多理论,最后给几个实战建议:

  1. 分区数怎么定? 我一般按目标吞吐量来算。假设单个 Partition 能处理 10MB/s,你期望 100MB/s,那就至少 10 个分区。再留 20% 的余量。
  2. 副本因子设多少? 开发测试用 1,预发用 2,生产至少 3。如果数据极其重要,可以设 4 或 5,但磁盘占用也会翻倍。
  3. Topic 命名规范:我习惯用 <业务域>-<数据主题>-<环境> 的格式,比如 order-payment-prod。这样一眼就能看出用途。
  4. 不要频繁创建删除 Topic:每次操作都会触发元数据变更,影响集群稳定性。

嗯,Topic 和 Partition 这块内容就这些。你只要把分区机制和副本策略搞明白,Kafka 的骨架就算搭稳了。后面咱们再聊消息生产和消费的细节,那才是真正跑起来的时候。