Kafka 集群部署与主题管理实战:三节点 KRaft 模式从零搭建
Kafka 3.3 之后 KRaft 模式正式可用于生产,3.9 起 ZooKeeper 彻底退出舞台。新集群没必要再维护一套 ZooKeeper:KRaft 把元数据管理收进 Kafka 自身,节点同时承担 Broker 与 Controller 角色,部署少一层组件,故障定位也少一层日志。本文在一台 Rocky Linux 9、Kafka 3.9 的口径下,用三台节点(kafka-01 / kafka-02 / kafka-03)走完从配置、格式化、启动到主题管理的完整流程,适用于中小规模业务消息、日志收集、数据管道的第一套生产集群。
为什么用 KRaft:与 ZooKeeper 模式的区别
旧模式下 Broker 把元数据写进 ZooKeeper,集群扩缩容、分区重分配都要等 ZK 同步;KRaft 改为用一个内部主题(__cluster_metadata)以 Raft 协议在 Controller 节点之间复制元数据,元数据变更的延迟从秒级降到毫秒级。部署上的变化有三点:一是不再需要单独安装和调优 ZooKeeper;二是配置文件从 server.properties 变为 config/kraft/server.properties,新增 process.roles、node.id、controller.quorum.voters 三组关键配置;三是集群首次启动前必须先格式化元数据目录,这一步做错顺序是新手最常见的翻车点。
前置条件
- 三台服务器,主机名 kafka-01、kafka-02、kafka-03,内网互通且主机名可解析(写 /etc/hosts 即可);
- JDK 17 已安装(Kafka 3.9 要求 Java 11+,生产建议 17);
- 数据盘单独挂载到 /data,Kafka 日志目录放数据盘,不要与系统盘混用;
- 防火墙放行 9092(客户端访问)与 9093(Controller 通信);
- 三台机器时间已同步(chrony),时钟漂移会引发选举与证书类怪问题。
第一步:安装与目录规划
三台节点执行相同操作。解压安装包到 /opt,创建专用用户与数据目录:
useradd -r -s /sbin/nologin kafka
tar -xzf kafka_2.13-3.9.0.tgz -C /opt/
ln -s /opt/kafka_2.13-3.9.0 /opt/kafka
mkdir -p /data/kafka/data /data/kafka/meta
chown -R kafka:kafka /data/kafka /opt/kafka_2.13-3.9.0
这里把消息数据(data)与 KRaft 元数据(meta)分开放在两个目录:元数据量小但对一致性敏感,与消息数据分盘/分目录便于单独备份和排查。
第二步:编写 KRaft 配置
编辑 /opt/kafka/config/kraft/server.properties,三台节点的差异只有 node.id 与 listeners 中的主机名,其余保持一致:
# 角色:broker + controller 合一,小集群的标准做法
process.roles=broker,controller
# 节点编号,全集群唯一,三台分别为 1、2、3
node.id=1
# Controller 选民名单:id@主机:9093,三台写法完全相同
controller.quorum.voters=1@kafka-01:9093,2@kafka-02:9093,3@kafka-03:9093
# PLAINTEXT 给客户端,CONTROLLER 给控制器内部通信
listeners=PLAINTEXT://kafka-01:9092,CONTROLLER://kafka-01:9093
inter.broker.listener.name=PLAINTEXT
controller.listener.names=CONTROLLER
# 对外公布地址:必须写客户端能解析的主机名,不能留空靠默认
advertised.listeners=PLAINTEXT://kafka-01:9092
# 元数据目录与消息数据目录
metadata.log.dir=/data/kafka/meta
log.dirs=/data/kafka/data
# 分区默认副本数与内置主题副本数,三节点集群设为 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
三个要点:第一,controller.quorum.voters 必须与每台的 node.id 一一对应,写错一位数字集群就选不出主;第二,advertised.listeners 是客户端连接后拿到的跳转地址,写成内网 IP 或错误主机名会出现「能连上但发不了消息」的经典症状;第三,min.insync.replicas=2 配合生产者 acks=all 是数据不丢的底线,两者缺一不可。
第三步:生成集群 ID 并格式化
KRaft 集群需要一个全集群统一的 ID。先在一台机器生成,然后在三台节点上分别用同一个 ID 格式化元数据目录:
# 生成集群 ID(只生成一次,三台共用)
/opt/kafka/bin/kafka-storage.sh random-uuid
# 假设输出为 kR7f2xQ9S6mZ8vN4pL0aBc(示例值)
# 三台节点逐台执行格式化
/opt/kafka/bin/kafka-storage.sh format \
-t kR7f2xQ9S6mZ8vN4pL0aBc \
-c /opt/kafka/config/kraft/server.properties
注意:format 只能在元数据目录为空时执行;如果启动失败后想重来,必须先清空 metadata.log.dir 再重新格式化,且三台必须用同一个集群 ID,否则节点会互相拒绝加入。
第四步:以 systemd 方式启动
三台节点各写一个服务单元,然后依次启动(顺序不敏感,Controller 会自动选举):
[Unit]
Description=Apache Kafka (KRaft)
After=network.target
[Service]
Type=simple
User=kafka
Environment="KAFKA_HEAP_OPTS=-Xms4g -Xmx4g"
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
LimitNOFILE=100000
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now kafka
# 查看集群元数据状态,确认三台都在 voters 列表中且有 leader
/opt/kafka/bin/kafka-metadata-quorum.sh \
--bootstrap-server kafka-01:9092 describe --status
堆内存按机器内存的 1/4 到 1/3 给即可,Kafka 大量使用页缓存做读写,没必要把堆开得很大;文件句柄上限则要放宽,默认的 1024 在分区多时会先爆。
第五步:主题管理与规划口径
主题的分区数与副本数要在创建时想清楚:副本数生产环境固定 3;分区数按「目标吞吐 ÷ 单分区吞吐」估算,并预留一倍扩容空间——分区只能增不能减,建多了浪费句柄,建少了后期扩分区会打乱按 key 的顺序性。创建与查看命令:
# 创建业务主题:12 分区、3 副本、保留 7 天
/opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka-01:9092 \
--create --topic order-events \
--partitions 12 --replication-factor 3 \
--config retention.ms=604800000
# 查看主题详情,重点看每分区的 Leader 与 Isr 是否齐全
/opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka-01:9092 \
--describe --topic order-events
# 修改已有主题的保留时间(示例:缩短为 3 天)
/opt/kafka/bin/kafka-configs.sh --bootstrap-server kafka-01:9092 \
--alter --entity-type topics --entity-name order-events \
--add-config retention.ms=259200000
验证:生产消费连通性
集群起来后用自带工具做一次端到端验证,并把其中一台节点停掉再验一次,确认副本与选举行为符合预期:
# 生产 5 条消息
/opt/kafka/bin/kafka-console-producer.sh \
--bootstrap-server kafka-01:9092 --topic order-events
# 输入任意内容后 Ctrl+C 退出
# 从头消费,应能读到刚写入的消息
/opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server kafka-02:9092 --topic order-events \
--from-beginning --max-messages 5
# 查看消费组与积压
/opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server kafka-01:9092 \
--describe --group test-group
关键监控指标
日常巡检抓四个数就够定位大多数问题:UnderReplicatedPartitions(副本不足的分区数,应恒为 0)、OfflinePartitionsCount(无主分区数,应恒为 0)、各 Broker 的磁盘使用率与日志目录大小、消费组的 Lag 趋势。这四个指标可以通过 JMX 暴露后接入 Prometheus,Exporter 用 kafka_exporter 即可,不需要额外部署重组件。
常见问题
1. 节点起不来,日志反复刷选举超时
先查三台的 controller.quorum.voters 是否完全一致、9093 端口是否互通、集群 ID 是否同一。再查 metadata.log.dir 是否被用不同 ID 格式化过——出现 InconsistentClusterIdException 就是这个原因,清空元数据目录重新 format。
2. ISR 持续收缩,分区副本跟不上
ISR 收缩说明从副本同步太慢,常见原因是磁盘 IO 打满、网络抖动或 replica.fetch.max.bytes 与消息体大小不匹配。先 describe 主题看哪些分区在收缩,再看对应 Broker 的磁盘 await 与网卡流量,不要一上来就调大 replica.lag.time.max.ms 掩盖问题——那只会让数据丢失窗口变大。
3. 磁盘被日志吃满
多半是 retention 配置缺失或某个主题流量暴涨。先用 kafka-log-dirs.sh 看各主题的实际占用,再核对 retention.ms 与 retention.bytes 是否按规划设置;长期方案是给每个主题建保留策略并接入磁盘使用率告警,而不是靠人工清理。
4. 客户端能连接但发送超时
九成是 advertised.listeners 写了客户端解析不了的地址。用 kafka-console-producer 指定不同节点的 bootstrap 地址交叉验证,并让客户端机器实际解析并 telnet 一次返回的 Broker 地址。
小结
KRaft 让 Kafka 集群的部署回到「三台机器、一份配置、一遍格式化」的简单形态。真正决定集群稳定性的不是安装动作,而是三件事:监听器地址写对、副本与 min.insync.replicas 的口径定死、每个主题在创建时就想清分区与保留策略。把这三件事做对,后面的扩容与运维都是按部就班的活。






