ZooKeeper 迁移到 KRaft 是 Kafka 3.x → 4.0 的必经之路。这篇不转述文档,而是真的把一个在线集群迁一遍:先起 KRaft controller,滚动重启 broker 触发元数据迁移,再把 broker 切成纯 KRaft,最后停掉 ZooKeeper,全程有生产者持续写入,事后核对数据与元数据。
主题: [('__consumer_offsets', '50', '3'), ('mig', '3', '3'), ('t1', '3', '3'), ('t2', '3', '3')]
消费组 g1 的 t1 各分区 CURRENT-OFFSET: [('2', '41'), ('1', '159'), ('0', '0')]
后台生产者已写入 60 条(每 20 ms 一条,同步等待 acks=all)
ZooKeeper 状态: {'controller': '{"version":2,"brokerid":1,"timestamp":"1790833827967","kraftControllerEpoch":-1}', 'migration': False, 'brokers': ['1', '2', '3']}起点的 ZooKeeper 节点见上一篇(Kafka 在 ZooKeeper 里存什么)。
[2026-10-01 13:50:44,360] WARN inter.broker.protocol.version is deprecated in KRaft mode as of 3.3 and will only be read when first upgrading from a KRaft prior to 3.3. See kafka-storage.sh help for d
controller 日志里与迁移有关的行:
INFO [ZooKeeperClient KRaft Migration] Initializing a new session to 127.0.0.1:7300. (kafka.zookeeper.ZooKeeperClient)
INFO [ZooKeeperClient KRaft Migration] Waiting until connected. (kafka.zookeeper.ZooKeeperClient)
INFO [ZooKeeperClient KRaft Migration] Connected. (kafka.zookeeper.ZooKeeperClient)
INFO [ControllerRegistrationManager id=3000 incarnation=IKnKCIK7Say2VAOXu6F0ZA] sendControllerRegistration: attempting to send ControllerRegistrationRequestData(controllerId=3000, incarnationId=IKnKCIK7Say2VAOX
INFO [KRaftMigrationDriver id=3000] Initial migration of ZK metadata is not done. (org.apache.kafka.metadata.migration.KRaftMigrationDriver)
INFO [KRaftMigrationDriver id=3000] 3000 transitioning from UNINITIALIZED to INACTIVE state (org.apache.kafka.metadata.migration.KRaftMigrationDriver)
INFO [MetadataLoader id=3000] InitializeNewPublishers: initializing KRaftMigrationDriver with a snapshot at offset 4 (org.apache.kafka.image.loader.MetadataLoader)
INFO [KRaftMigrationDriver id=3000] Became active migration driver in 38508458 ns. Transitioned migration state from ZkMigrationLeadershipState{kraftControllerId=-1, kraftControllerEpoch=-1, kraftMetadataOffset
此时 ZooKeeper: {'controller': '{"version":2,"brokerid":1,"timestamp":"1790833827967","kraftControllerEpoch":-1}', 'migration': True, 'brokers': ['1', '2', '3']} broker 1 已用迁移配置重启(6 s)
broker 2 已用迁移配置重启(11 s)
broker 3 已用迁移配置重启(17 s)
controller 日志里迁移相关的行(最后 10 行):
INFO [KRaftMigrationDriver id=3000] Completed migration of metadata from ZooKeeper to KRaft. 66 records were generated in 100 ms across 1 batches. The average time spent waiting on a batch was 26.00 ms. The record types were {TOPI
INFO [KRaftMigrationDriver id=3000] Finished initial migration of ZK metadata to KRaft in 3311791 ns. Transitioned migration state from ZkMigrationLeadershipState{kraftControllerId=3000, kraftControllerEpoch=1, kraftMetadataOffset
INFO [KRaftMigrationDriver id=3000] 3000 transitioning from ZK_MIGRATION to SYNC_KRAFT_TO_ZK state (org.apache.kafka.metadata.migration.KRaftMigrationDriver)
INFO [KRaftMigrationDriver id=3000] Performing a full metadata sync from KRaft to ZK. (org.apache.kafka.metadata.migration.KRaftMigrationDriver)
INFO [KRaftMigrationDriver id=3000] Did not make any ZK writes when reconciling with KRaft state. (org.apache.kafka.metadata.migration.KRaftMigrationDriver)
INFO [KRaftMigrationDriver id=3000] 3000 transitioning from SYNC_KRAFT_TO_ZK to KRAFT_CONTROLLER_TO_BROKER_COMM state (org.apache.kafka.metadata.migration.KRaftMigrationDriver)
INFO [KRaftMigrationDriver id=3000] Expected driver state SYNC_KRAFT_TO_ZK but found KRAFT_CONTROLLER_TO_BROKER_COMM. Not running this event SyncKRaftMetadataEvent. (org.apache.kafka.metadata.migration.KRaftMigrationDriver)
INFO [KRaftMigrationDriver id=3000] Sending RPCs to broker before moving to dual-write mode using at offset and epoch OffsetAndEpoch(offset=117, epoch=1) (org.apache.kafka.metadata.migration.KRaftMigrationDriver)
INFO [KRaftMigrationDriver id=3000] 3000 transitioning from KRAFT_CONTROLLER_TO_BROKER_COMM to DUAL_WRITE state (org.apache.kafka.metadata.migration.KRaftMigrationDriver)
INFO [KRaftMigrationDriver id=3000] Expected driver state KRAFT_CONTROLLER_TO_BROKER_COMM but found DUAL_WRITE. Not running this event SendRPCsToBrokersEvent. (org.apache.kafka.metadata.migration.KRaftMigrationDriver)
ZooKeeper 状态: {'controller': '{"version":2,"brokerid":3000,"timestamp":"1790833864954","kraftControllerEpoch":1}', 'migration': True, 'brokers': ['1', '2', '3']}
此时 /brokers/topics 仍是: ['__consumer_offsets', 'mig', 't1', 't2'] (controller 已进入 DUAL_WRITE:KRaft 的元数据变更同时写回 ZooKeeper,迁移未完成前还能回滚)
kafka-metadata-quorum describe --status:
ClusterId: sgR5w7tVSz24GYvEk_UErQ
LeaderId: 3000
LeaderEpoch: 1
HighWatermark: 141
MaxFollowerLag: 0
MaxFollowerLagTimeMs: 0
CurrentVoters: [{"id": 3000, "directoryId": null, "endpoints": ["CONTROLLER://127.0.0.1:7910"]}]
CurrentObservers: [{"id": 3, "directoryId": "YHxOXhDv16rQGDlOgGJJbw"}, {"id": 1, "directoryId": "xsNmW3trQLSzh7VJGsD_JA"}, {"id": 2, "directoryId": "9Z0RWHLrU2XVvIwloNjnmA"}]每个 broker 重启期间,生产者会遇到 leader 切换(和controller 被杀类似),重试机制把它们吸收了。
broker 1 以 KRaft 模式重启
broker 2 以 KRaft 模式重启
broker 3 以 KRaft 模式重启
ZooKeeper 状态: {'controller': '{"version":2,"brokerid":3000,"timestamp":"1790833864954","kraftControllerEpoch":1}', 'migration': True, 'brokers': []} ZooKeeper 已停止;后台生产者继续写: 143 条/4 s
生产者总共写入 2702 条(同步确认),过程中出现的发送错误类型 {}
迁移后的主题: [('__consumer_offsets', '50', '3'), ('mig', '3', '3'), ('t1', '3', '3'), ('t2', '3', '3')] | 与迁移前一致: True
消费组 g1 的 t1 各分区 CURRENT-OFFSET: [('2', '41'), ('1', '159'), ('0', '0')] | 与迁移前一致: True
主题 mig 读回 2702 条:丢失(同步确认过的 id 里没读到)0 条,重复 0 条
生产者相邻两次确认之间的最长间隔 1.0 s(整个迁移过程;超过 1 s 的间隔共 1 个)这一步之后 ZooKeeper 完全没有参与。我没有测回滚路径,也没有核对文档里这一步之后是否还能回滚。