3. Kafka 元数据全景分析
说实话,很多做 Kafka 的同学,天天跟生产消费打交道,但真要问一句「Kafka 到底有哪些元数据」,能说全的还真不多。我刚开始带团队那会儿,有一次集群出了故障,排查了半天才发现是 Broker 配置不一致导致的。从那以后,我就养成了一个习惯——先把元数据地图画清楚。
这一章,我们就来把这幅地图画出来。我会从四个维度展开:集群元数据、Topic 元数据、消费元数据、配置元数据。你想想看,搞清楚了这些,后面做实时采集才有方向。
核心观点:Kafka 元数据不是孤立的,它们之间互相引用、互相依赖。Broker 挂了会影响 Topic 的副本分配,Topic 分区变化会影响 Consumer 的 Offset 提交。理解这些关联,才是元数据管理的精髓。
3.1 集群元数据:Broker 列表与 Controller 信息
集群元数据,说白了就是「这个集群里有哪些机器,谁在管事」。我见过不少运维同学,集群出问题了第一反应是看日志,其实先看一眼集群元数据,很多问题就一目了然了。
3.1.1 Broker 列表
Broker 列表记录了集群中所有存活的 Broker 节点。每个 Broker 有一个唯一的 ID,以及对应的主机名、端口、Rack 信息等。Kafka 通过 ZooKeeper 或 KRaft 来维护这份列表。
获取 Broker 列表的方式很简单:
# 通过命令行
./kafka-brokers.sh --bootstrap-server localhost:9092 --describe
# 通过 AdminClient API
Properties props = new Properties();
props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
try (AdminClient admin = AdminClient.create(props)) {
DescribeClusterResult result = admin.describeCluster();
Collection<Node> nodes = result.nodes().get();
for (Node node : nodes) {
System.out.println("Broker ID: " + node.id() +
", Host: " + node.host() +
", Port: " + node.port());
}
}
我的经验:我曾经遇到过一个诡异的问题——某个 Broker 明明进程还在,但集群里就是看不到它。后来发现是 ZooKeeper 会话超时了,但进程没退出。所以采集 Broker 列表时,别只看进程,要看它是否真的在集群注册表中。
3.1.2 Controller 信息
Controller 是 Kafka 集群的「大脑」。它负责管理分区 leader 的选举、副本分配等核心操作。集群中只有一个 Controller,如果它挂了,其他 Broker 会重新选举。
Controller 的信息包括:
- Controller ID:当前担任 Controller 的 Broker ID
- Controller 版本:Kafka 版本信息
- Controller 任期:从第几次选举开始
- 最近一次选举时间:方便排查故障
我个人习惯在元数据采集时,把 Controller 信息单独拎出来监控。为什么呢?因为 Controller 的频繁变更往往预示着集群不稳定。
注意:Controller 的选举不是瞬间完成的。在选举期间,集群的元数据操作会阻塞。如果你的采集任务恰好在此时发起,可能会拿到不一致的数据。建议加一个重试机制。
3.2 Topic 元数据:分区数、副本分配与 ISR 列表
Topic 元数据是元数据采集中最核心的部分。你想想看,生产者和消费者都是围绕 Topic 工作的,Topic 的元数据一旦出错,整个数据流就乱了。
3.2.1 分区数
每个 Topic 可以分成多个分区。分区数是 Topic 创建时指定的,但也可以后期调整。不过要注意,分区数只能增加,不能减少。
| 属性 | 说明 | 采集建议 |
|---|---|---|
| 分区总数 | Topic 当前的分区数量 | 与预期值对比,发现异常 |
| 分区 ID 列表 | 0, 1, 2, ... | 检查是否有缺失 |
| Leader 分布 | 每个分区的 leader 在哪个 Broker | 看是否均匀分布 |
3.2.2 副本分配
副本分配决定了每个分区的数据冗余在哪些 Broker 上。Kafka 默认会尽量把副本分散到不同的 Broker 上,以提高容错性。
获取副本分配的代码示例:
// 获取 Topic 的副本分配
DescribeTopicsResult result = admin.describeTopics(Arrays.asList("my-topic"));
TopicDescription topicDesc = result.values().get("my-topic").get();
for (TopicPartitionInfo partition : topicDesc.partitions()) {
System.out.println("Partition: " + partition.partition());
System.out.println(" Leader: " + partition.leader().id());
System.out.println(" Replicas: " +
partition.replicas().stream().map(Node::id).collect(Collectors.toList()));
System.out.println(" ISR: " +
partition.isr().stream().map(Node::id).collect(Collectors.toList()));
}
关键点:副本分配不是一成不变的。当 Broker 宕机或新增时,Kafka 会自动触发副本重分配。采集时要注意时间戳,避免拿到过时的分配信息。
3.2.3 ISR 列表
ISR(In-Sync Replicas)是「与 leader 保持同步的副本列表」。说白了,就是那些数据跟 leader 基本一致的副本。如果某个副本落后太多,就会被踢出 ISR。
ISR 列表的变化非常频繁,尤其是在高负载场景下。我建议采集 ISR 时,同时记录一下时间点和对应的 leader epoch,这样后续分析才有依据。
3.3 消费元数据:Consumer Group 与 Offset
消费元数据是排查数据丢失或重复消费的关键。我记得有一次,业务方反馈说数据丢了,我第一反应就是查 Consumer Group 的 Offset。
3.3.1 Consumer Group
每个 Consumer Group 有一个唯一的名称,以及一组消费者实例。Kafka 会记录每个 Group 的成员信息、分区分配策略、状态等。
常见的 Consumer Group 状态:
- Stable:正常运行
- Rebalancing:正在重新分配分区
- Empty:没有活跃消费者
- Dead:已超时未心跳
3.3.2 Offset
Offset 是消费者在每个分区上的消费位置。Kafka 把 Offset 保存在内部 Topic __consumer_offsets 中。
采集 Offset 时,我通常会关注三个值:
- 当前 Offset:消费者已经提交的位置
- Log End Offset (LEO):分区的最新消息位置
- Lag:LEO - 当前 Offset,表示消费延迟
避坑指南:我曾经遇到过 Offset 提交失败导致重复消费的问题。原因是消费者在处理消息时抛了异常,但 Offset 却提前提交了。所以采集 Offset 时,最好也采集一下消费者的状态和异常信息。
3.4 配置元数据:Topic 配置与 Broker 配置
配置元数据决定了 Kafka 的行为。很多性能问题,归根结底是配置不合理。
3.4.1 Topic 配置
每个 Topic 可以单独设置一些参数,比如:
| 配置项 | 默认值 | 说明 |
|---|---|---|
| retention.ms | 604800000 (7天) | 消息保留时间 |
| max.message.bytes | 1000012 | 单条消息最大大小 |
| cleanup.policy | delete | 日志清理策略 |
| compression.type | producer | 压缩类型 |
3.4.2 Broker 配置
Broker 配置是全局性的,影响整个集群。常见的包括:
- num.partitions:新建 Topic 的默认分区数
- default.replication.factor:默认副本因子
- log.retention.hours:日志保留时间
- zookeeper.connect:ZooKeeper 连接地址
注意:Broker 配置的变更需要重启才能生效。采集时如果发现配置与预期不符,要确认是临时修改还是持久化修改。我曾经踩过坑——改了配置忘了重启,结果采集到的还是旧值。