bigywater.tech
Kafka 内部原理 · 42

Kafka Controller 到底负责什么

直接用kafka-dump-log.sh读出__cluster_metadata这个内部topic的原始日志,看broker注册、topic创建、故障切换这些Controller的决定真实是怎么变成一条条具体记录的。

13种record类型
__cluster_metadata日志里真实记录的决策种类
fenced:1→-1
broker故障和恢复真实对应的两条记录

验证:__cluster_metadata不是一个抽象概念,是一个真实能读出来的topic

命令 · shell $ kafka-dump-log.sh --cluster-metadata-decoder \ --files kafka-lab/node2/data/__cluster_metadata-0/00000000000000000000.log \ | grep -oE "\"type\":\"[A-Z_]+\"" | sort | uniq -c | sort -rn
真实实测 · 本机执行输出
2214 NO_OP_RECORD
 242 PARTITION_CHANGE_RECORD
 116 PARTITION_RECORD
  12 BROKER_REGISTRATION_CHANGE_RECORD
  11 TOPIC_RECORD
  10 CONFIG_RECORD
   6 FEATURE_LEVEL_RECORD
   5 REGISTER_CONTROLLER_RECORD
   5 REGISTER_BROKER_RECORD
   2 PRODUCER_IDS_RECORD
   2 CLEAR_ELR_RECORD
   1 REMOVE_TOPIC_RECORD
   1 END_TRANSACTION_RECORD
   1 BEGIN_TRANSACTION_RECORD

这个topic真实就是Controller对外呈现的全部工作成果——每一种record类型真实对应Controller做过的一类决策。REGISTER_BROKER_RECORD 真实记录"这个broker什么时候加入集群";TOPIC_RECORD/PARTITION_RECORD 真实记录"这个topic叫什么、有几个分区、每个分区的副本和leader分别是谁";PARTITION_CHANGE_RECORD 高达242条,真实是本机做过的多次leader切换和ISR收缩/扩张留下的痕迹。

验证:创建一个topic,真实写下的是两条具体记录

真实实测 · 本机执行输出
offset: 74  {"type":"TOPIC_RECORD","data":{"name":"smoke-test","topicId":"SItw_6LbSwarMVjx45Ro3Q"}}
offset: 75  {"type":"PARTITION_RECORD","data":{"partitionId":0,"topicId":"SItw_6LbSwarMVjx45Ro3Q",
            "replicas":[2,3,1],"isr":[2,3,1],"leader":2,"leaderEpoch":0,"partitionEpoch":0, ...}}

执行一次kafka-topics.sh --create,Controller真实做的事情就是往这个日志里追加这两条记录:先分配一个topicId,再为每个分区真实决定副本落在哪几个broker上、初始leader是谁。这两条记录一旦真实写进了KRaft的复制日志,就会被同步给集群里的每一个broker——每个broker都真实在本地维护一份从这份日志重放出来的"世界观",谁是leader、该往哪里转发请求,全靠这份重放结果,不需要额外去问Controller。

验证:kill一个broker,Controller真实把它标记成fenced

真实实测 · 本机执行输出
{"type":"BROKER_REGISTRATION_CHANGE_RECORD","data":{"brokerId":1,"brokerEpoch":2099,"fenced":1}}
{"type":"BROKER_REGISTRATION_CHANGE_RECORD","data":{"brokerId":1,"brokerEpoch":2467,"fenced":-1}}

kill -9掉broker1之后,Controller真实检测到它的心跳超时,往日志里追加了一条fenced:1的记录——这条记录真实的效果是"从现在起,任何分区都不应该再把broker1当成可用的副本",紧接着的PARTITION_CHANGE_RECORD(前面那组统计里的242条之一)真实把原本由broker1领导的分区重新指派了新leader。重启broker1之后,真实又追加了一条fenced:-1(取消隔离),它才重新真实变回一个可以接收leader指派的候选者。

这就是本系列前面几篇里"broker挂了、leader自动切换"真实背后发生的事:不是每个broker各自感知到同伴挂了然后自己商量出一个新leader,而是Controller作为真实唯一的决策者,检测故障、更新fencing状态、重新分配leader,把结果写成日志记录,其它broker只是真实被动地重放这份日志、照着执行。Controller的职责可以归纳成一句话:它是集群里唯一被允许对"谁是broker、谁是leader、有哪些topic"这类元数据做出决定的角色,其它所有节点都只负责真实执行它写下的决定。

分步演示:Controller的工作成果真实长什么样

每一步都是本文上面用kafka-dump-log.sh读出来的原始记录。

Controller的每一个决定都真实是日志里的一条记录
这一步新验证的记录

    参考与说明

    • 数据来自本机3节点Kafka 4.3.1集群(KRaft模式),直接用kafka-dump-log.sh --cluster-metadata-decoder读取__cluster_metadata分区0的原始日志文件。
    • 和《KRaft Controller Quorum 如何选主》一起看:这篇看Controller做了什么决定,那篇看这些决定真实是怎么在多个Controller节点之间达成一致的。
    • 没有验证:ZooKeeper模式下Controller的等价实现(本机Kafka 4.3.1已经不支持ZK模式,见下一篇)。
    京ICP备2026067502号-1京公网安备11010502063487号