外观
KRaft 模式集群:不再需要 Zookeeper
KRaft 把元数据管理收进 Kafka 自己(基于 Raft 的 controller quorum)。Kafka 4.0 起只剩这一种模式,新集群没有理由再用 ZK。
场景
- 新建集群(首选)。
- 想把三节点 ZK + 三节点 broker 精简成三节点。
- 老集群在 3.6+,评估原地迁移。
相比 ZK 模式的好处:
| 维度 | ZK 模式 | KRaft |
|---|---|---|
| 组件 | ZK 集群 + Kafka | 只有 Kafka |
| 元数据规模上限 | 分区数 ~20 万时 ZK 成为瓶颈 | 百万级分区 |
| controller 切换 | ZK 选举,秒级到几十秒 | Raft,通常 < 1s |
| 部署复杂度 | 两套配置、两套端口 | 一套 |
| 监控 | ZK + Kafka | 只有 Kafka |
一、拓扑
三台机器,每台同时是 broker 和 controller(combined 模式),小集群最省机器。
| 机器 | node.id | broker 端口 | controller 端口 |
|---|---|---|---|
| 10.0.0.31 | 1 | 9092 | 9093 |
| 10.0.0.32 | 2 | 9092 | 9093 |
| 10.0.0.33 | 3 | 9092 | 9093 |
规模大(broker > 10 或分区 > 10 万)时改成 isolated 模式:3 台只做 controller,其余只做 broker。controller 不处理客户端请求,抖动更小。
二、配置
ini
# /opt/kafka/config/kraft/server.properties (10.0.0.31)
process.roles=broker,controller
node.id=1
# 三台的投票成员列表,写成 <node.id>@<host>:<controller端口>
controller.quorum.voters=1@10.0.0.31:9093,2@10.0.0.32:9093,3@10.0.0.33:9093
# 两个监听器:客户端用 PLAINTEXT,controller 用 CONTROLLER
listeners=PLAINTEXT://10.0.0.31:9092,CONTROLLER://10.0.0.31:9093
advertised.listeners=PLAINTEXT://10.0.0.31:9092
inter.broker.listener.name=PLAINTEXT
controller.listener.names=CONTROLLER
listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
log.dirs=/data/kafka/logs
num.partitions=3
default.replication.factor=3
min.insync.replicas=2
offsets.topic.replication.factor=3
transaction.state.log.replication.factor=3
transaction.state.log.min.isr=2
auto.create.topics.enable=false
log.retention.hours=168三处必须逐台改:node.id、listeners、advertised.listeners。
| 配置 | 说明 |
|---|---|
process.roles | broker,controller 混部;纯 broker 写 broker,纯 controller 写 controller |
node.id | 集群内唯一,和 ZK 模式的 broker.id 是两套东西,不要混 |
controller.quorum.voters | 必须包含全部 controller;漏一个会导致 quorum 永远选不出 leader |
controller.listener.names | 名字要和 listener.security.protocol.map 里的一致 |
inter.broker.listener.name | broker 之间通信走哪个监听器 |
CONTROLLER 端口绝不能暴露公网
controller 监听器没有认证,能连上就等于能改整个集群的元数据。安全组只放通内网。也不要把 9093 配成 0.0.0.0 后直接 advertised.listeners 里公开。
三、格式化存储目录
只在第一次部署时做,且每台都要做一次。
bash
export KAFKA_HOME=/opt/kafka
# 1. 生成集群 ID(只生成一次,三台共用同一个)
KAFKA_CLUSTER_ID=$($KAFKA_HOME/bin/kafka-storage.sh random-uuid)
echo $KAFKA_CLUSTER_IDtext
hB1q2wErTz6Y8uI0oP3aSgbash
# 2. 每台执行格式化
$KAFKA_HOME/bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID -c $KAFKA_HOME/config/kraft/server.propertiestext
Formatting /data/kafka/logs with metadata.version 3.9-IV0.解释:
metadata.version是元数据版本,决定能用哪些特性。生产要显式指定成目标版本(如3.9-IV0),不指定会用 jar 里最高的,之后降版本会起不来。- 三台的
KAFKA_CLUSTER_ID必须一致,否则节点之间拒绝组成集群,日志报Cluster ID mismatch。 - 格式化会写
meta.properties到log.dirs。目录非空时格式化会失败,这是保护机制。
想锁定元数据版本:
bash
$KAFKA_HOME/bin/kafka-storage.sh format -t $KAFKA_CLUSTER_ID \
-c $KAFKA_HOME/config/kraft/server.properties \
--release-version 3.9四、启动
bash
chown -R kafka:kafka /data/kafka
systemctl enable --now kafka
journalctl -u kafka -n 40 --no-pagersystemd 与 ZK 模式基本一致,只是 ExecStart 换成 KRaft 配置:
ini
[Service]
Type=simple
User=kafka
Group=kafka
Environment="JAVA_HOME=/usr/local/jdk-17"
ExecStart=/opt/kafka/bin/kafka-server-start.sh /opt/kafka/config/kraft/server.properties
ExecStop=/opt/kafka/bin/kafka-server-stop.sh
Restart=on-failure
RestartSec=5
LimitNOFILE=65535正常启动日志:
text
[2026-10-27 19:40:11,204] INFO [RaftManager nodeId=1] Completed transition to Leader with epoch 1 (kafka.raft.KafkaRaftManager)
[2026-10-27 19:40:11,662] INFO [KafkaRaftServer nodeId=1] Kafka Server started (kafka.server.KafkaRaftServer)Completed transition to Leader 只会在 leader 那台出现,其余两台是 Completed transition to Follower。
五、验证 quorum
bash
/opt/kafka/bin/kafka-metadata-quorum.sh --bootstrap-server 10.0.0.31:9092 describe --statustext
ClusterId: hB1q2wErTz6Y8uI0oP3aSg
LeaderId: 1
LeaderEpoch: 3
HighWatermark: 271
MaxFollowerLag: 0
MaxFollowerLagTimeMs: 0
CurrentVoters: [1,2,3]
CurrentObservers: []bash
/opt/kafka/bin/kafka-metadata-quorum.sh --bootstrap-server 10.0.0.31:9092 describe --replicationtext
NodeId Host LogEndOffset Lag LastFetchTimestamp LastCaughtUpTimestamp Status
1 10.0.0.31:9093 271 0 1761550211884 1761550211884 Leader
2 10.0.0.32:9093 271 0 1761550211902 1761550211902 Follower
3 10.0.0.33:9093 271 0 1761550211891 1761550211891 Follower读法:
CurrentVoters [1,2,3]三票齐全。- 两个 follower 的
Lag都是 0,Status不是Leader以外的问题状态。 - 如果只有一个节点,
CurrentVoters只有它自己,说明另外两台没连上——检查controller.quorum.voters和 9093 端口。
再确认 broker 都注册了:
bash
/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server 10.0.0.31:9092 | grep -c 'id:'text
3六、冒烟测试
与 ZK 模式完全一致:
bash
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 10.0.0.31:9092 \
--create --topic smoke --partitions 3 --replication-factor 3
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 10.0.0.31:9092 --describe --topic smoketext
Topic: smoke TopicId: 3fT8mQ2nR5vW7xY9zA1bCy PartitionCount: 3 ReplicationFactor: 3 Configs:
Topic: smoke Partition: 0 Leader: 1 Replicas: 1,2,3 Isr: 1,2,3
Topic: smoke Partition: 1 Leader: 2 Replicas: 2,3,1 Isr: 2,3,1
Topic: smoke Partition: 2 Leader: 3 Replicas: 3,1,2 Isr: 3,1,2RF=3 + ISR=3 才算真三副本
Replicas 是"分配了哪些副本",Isr 是"当前同步的副本"。只有 Isr 长度为 3 时才有三份实时数据。看到 Isr: 1,2 就该查第三台。
七、controller 挂了会怎样
bash
systemctl stop kafka # 在 leader 那台(node.id=1)bash
/opt/kafka/bin/kafka-metadata-quorum.sh --bootstrap-server 10.0.0.32:9092 describe --statustext
ClusterId: hB1q2wErTz6Y8uI0oP3aSg
LeaderId: 2
LeaderEpoch: 4
CurrentVoters: [1,2,3]LeaderId 变成 2,LeaderEpoch 加 1,切换通常在 1 秒内完成。broker 侧不受影响(数据面照常读写)。
注意:combined 模式下,挂掉的这台同时少了一个 broker,它上面的 leader 分区要在剩余两台之间重新选举,这段时间该分区不可用几百毫秒到几秒。
八、ZK 迁 KRaft
3.6 起提供原地迁移(preview),3.9 已可用,但单向不可回退。
大致过程(每一阶段都要停业务验证):
- 给所有 broker 配好 controller 相关参数,并打开
zookeeper.metadata.migration.enable=true,重启。 - 启动 controller quorum,等元数据从 ZK 复制到 KRaft。
- 切换
process.roles去掉zookeeper.connect,再滚动重启。 - 确认
kafka-metadata-quorum.sh describe --status正常后,下线 ZK。
迁移期间不要做任何 topic 变更
扩分区、改副本、删 topic 都可能触发元数据不一致。迁移窗口内只允许读写数据。
大多数团队更稳妥的做法:新建 KRaft 集群 → 用 MirrorMaker 2 双向同步 → 业务逐个切 bootstrap 地址 → 下线老集群。虽然多花机器,但随时可回退。
迁移步骤随版本变化
zookeeper.metadata.migration.enable 的具体行为和配套命令在 3.6 / 3.7 / 3.9 之间有调整。执行前以当前版本的官方 upgrade 文档为准,本文不给出跨版本通用的完整命令序列(待验证)。
九、踩过的坑
| 现象 | 根因 | 处置 |
|---|---|---|
启动报 Cluster ID mismatch | 三台 KAFKA_CLUSTER_ID 不同 | 用同一个 ID 重新格式化(会清空数据) |
格式化报 Log directory ... is not empty | 目录里有旧数据 | 确认可清空后删掉 meta.properties 和日志 |
CurrentVoters 只有自己 | controller.quorum.voters 少写/写错 IP | 三台配置必须完全一致 |
| 一直选不出 leader | 9093 不通,或投票成员不足半数 | 放通端口,确认 3 台中至少 2 台在线 |
| 客户端能连但生产失败 | inter.broker.listener.name 没配 | 补上,指向 PLAINTEXT |
| Kafka 4.x 用 Java 11 起不来 | 4.0 要求 Java 17+ | 换 JDK 17 |
| 升级 jar 后起不来,报元数据版本不兼容 | 格式化时用了过高的 metadata.version | 升级时用 --release-version 指定,且不可随意降 |
| 想回退到 ZK 模式 | KRaft 单向 | 只能重建集群 + 数据迁移 |