3. Topic 与 Partition:主题、分区与副本

Topic 是 Kafka 里最基础的概念。说白了,它就是一条数据流,一个逻辑上的分类。你往 Topic 里写消息,再从 Topic 里读消息,就这么简单。

但真正让 Kafka 能扛住海量数据的,是 Partition。我刚开始接触 Kafka 时,总觉得 Topic 和 Partition 是一回事。后来在线上压测时才发现,如果只用一个 Partition,吞吐量根本起不来。嗯,这里要好好拆解一下。

3.1 主题(Topic)是什么?

Topic 是消息的逻辑容器。你可以把它理解成数据库里的一张表,或者文件系统里的一个文件夹。生产者往 Topic 里写数据,消费者从 Topic 里读数据。

每个 Topic 都有一个名字,在整个 Kafka 集群里唯一。我个人习惯用业务域来命名,比如 order_paid、user_login,这样一眼就能看出用途。

核心特性:

  • Topic 是逻辑概念,不直接存储数据
  • 一个 Topic 可以有多个生产者和消费者
  • Topic 内的消息是有序的吗?不一定,这取决于分区数量

3.2 分区(Partition)机制

Partition 才是真正存储数据的地方。每个 Partition 是一个有序的、不可变的日志文件。消息追加到 Partition 尾部,每个消息都有一个唯一的偏移量(offset)。

为什么需要分区?我举个例子。假设你只有一个 Partition,所有消息都往这一个文件里写。写入速度受限于磁盘 IO,读取也只能串行。但如果你把 Topic 拆成 3 个 Partition,就可以同时往 3 个文件里写,读取时也能并行。吞吐量直接翻倍。

分区数越多,并行度越高。但也不是越多越好。我记得有一次,我把一个 Topic 设成了 100 个分区,结果集群的元数据同步压力暴增,Leader 选举也变慢了。所以分区数要合理规划。

分区数 优点 缺点
少(1-3) 元数据小,管理简单 吞吐量受限,并行度低
中(4-16) 吞吐量好,负载均衡 需要合理分配
多(>50) 极高并行度 元数据膨胀,选举变慢

我的建议:分区数一般设置为消费者组内消费者数量的整数倍。比如你有 4 个消费者,分区数可以设为 4、8、12。这样每个消费者能均匀分配分区。

3.3 分区策略

生产者往 Topic 里发消息时,到底发到哪个 Partition?这不是随机的,而是由分区策略决定的。Kafka 提供了几种默认策略,你也可以自定义。

3.3.1 轮询策略(Round Robin)

这是默认策略。消息依次发送到每个 Partition,循环往复。适合消息之间没有顺序要求的场景。比如日志收集,每条日志独立,不需要保证顺序。

3.3.2 哈希策略(Hash)

根据消息的 key 计算哈希值,然后对分区数取模。相同 key 的消息一定会进入同一个 Partition。这保证了同一个 key 的消息是有序的。

我在项目中遇到过一个问题:某个订单系统的消息必须按订单 ID 有序处理。如果用轮询策略,同一个订单的消息可能分散到不同分区,消费者处理时就会乱序。后来我改用哈希策略,以订单 ID 作为 key,问题就解决了。

3.3.3 自定义策略

如果默认策略不满足需求,你可以实现 Partitioner 接口。比如按地域路由,把北京的消息发到 Partition 0,上海的发到 Partition 1。

// 自定义分区器示例
public class RegionPartitioner implements Partitioner {
    @Override
    public int partition(String topic, Object key, byte[] keyBytes,
                         Object value, byte[] valueBytes, Cluster cluster) {
        String region = extractRegion(key.toString());
        if ("beijing".equals(region)) {
            return 0;
        } else if ("shanghai".equals(region)) {
            return 1;
        }
        return 2; // 默认分区
    }
}

注意:自定义分区器要保证幂等性。同一个 key 每次计算的结果必须一致。否则会导致消息乱序,甚至数据丢失。

3.4 分区与副本的关系

副本(Replica)是 Kafka 高可用的基石。每个 Partition 可以有多个副本,分布在不同的 Broker 上。副本分为 Leader 和 Follower。

  • Leader:负责处理读写请求。所有生产者和消费者都只跟 Leader 交互。
  • Follower:只负责从 Leader 同步数据。不对外提供服务。

为什么这样设计?你想想看,如果所有副本都能读写,数据一致性就很难保证。Leader 统一处理,Follower 被动同步,这样数据就不会乱。

副本数一般设为 2 或 3。设成 1 的话,Broker 挂了数据就丢了。设得太多,同步开销又太大。我一般建议生产环境至少 2 个副本,核心业务用 3 个。

这里有个关键参数:min.insync.replicas。它指定了最少需要多少个副本同步成功,才算写入成功。比如副本数是 3,min.insync.replicas 设为 2,那么至少 2 个副本写入成功,生产者才会收到 ack。这样即使一个 Broker 挂了,数据也不会丢。

避坑指南:我曾经把 min.insync.replicas 设成了 3,副本数也是 3。结果一个 Broker 宕机后,所有写入都失败了。因为只剩 2 个副本在线,永远达不到 3 个同步的要求。后来我改成 2,问题就解决了。

3.5 分区与副本的关系图

下面这张图展示了 Topic、Partition、副本之间的关系。一个 Topic 有多个 Partition,每个 Partition 有多个副本,副本分布在不同的 Broker 上。

Topic、Partition 与副本关系图 Topic: order_paid Partition 0 Partition 1 Partition 2 Leader Follower 1 Leader Follower 2 Leader F3 Broker 1 Broker 2 Broker 3 说明 • 每个 Partition 有 1 个 Leader 和多个 Follower • Leader 负责读写,Follower 只同步数据 • 副本分布在不同的 Broker 上,保证高可用

3.6 分区与副本的配置建议

最后,我总结几条实战经验。这些是我在多个项目中踩过坑之后得出的结论。

  • 分区数:根据预期吞吐量计算。一般每秒 100MB 的写入量,8 个分区就够了。不要盲目设大。
  • 副本数:生产环境至少 2,核心业务用 3。副本数不要超过 Broker 数量。
  • min.insync.replicas:设为副本数减 1。比如副本数是 3,就设成 2。
  • 分区策略:需要顺序保证时用哈希策略,不需要时用轮询策略。

小技巧:如果你不确定分区数,可以先设成 4。后续可以通过 Kafka 的 kafka-reassign-partitions.sh 工具动态调整。但注意,调整分区数会导致数据重分布,尽量在低峰期操作。