直接用kafka-dump-log.sh读出__cluster_metadata这个内部topic的原始日志,看broker注册、topic创建、故障切换这些Controller的决定真实是怎么变成一条条具体记录的。
命令 · 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收缩/扩张留下的痕迹。
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。
{"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指派的候选者。
每一步都是本文上面用kafka-dump-log.sh读出来的原始记录。