外观
Zookeeper 模式三节点集群
Kafka 3.x 仍支持 Zookeeper 模式,存量集群大量是这个形态。新集群请直接用 KRaft,但维护老集群必须懂这套。
场景
- 接手一个 Kafka 2.x/3.x 的老集群,需要扩缩容、排障。
- 领导要求"和现有环境保持一致"。
- 想理解
controller、broker.id、/brokers/ids这些概念的来源。
Kafka 4.0 已彻底移除 Zookeeper 支持,本章内容只适用于 3.x 及更早。
一、拓扑
| 机器 | 角色 | 端口 |
|---|---|---|
| 10.0.0.21 | ZK + broker 1 | 2181 / 9092 |
| 10.0.0.22 | ZK + broker 2 | 2181 / 9092 |
| 10.0.0.23 | ZK + broker 3 | 2181 / 9092 |
ZK 必须奇数台(3/5),broker 数量随意。三台机器混部在小规模场景很常见。
二、安装
bash
cd /opt
wget https://downloads.apache.org/kafka/3.9.0/kafka_2.13-3.9.0.tgz
tar zxf kafka_2.13-3.9.0.tgz
ln -sfn /opt/kafka_2.13-3.9.0 /opt/kafka
java -versiontext
openjdk version "11.0.24" 2024-07-16 LTS
OpenJDK Runtime Environment (Red_Hat-11.0.24.0.7-1) (build 11.0.24+7-LTS)解释:Kafka 3.9 的 broker 要求 Java 11+(客户端还兼容 8)。JDK 8 启动会直接报 Unsupported class file major version。安装 JDK 见 JDK 安装。
三、Zookeeper 配置
三台的 zoo.cfg 完全一样,只有 myid 文件不同。
ini
# /opt/kafka/config/zookeeper.properties
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/data/zookeeper
clientPort=2181
maxClientCnxns=60
# 自动清理旧快照和事务日志,否则 dataDir 会慢慢涨满
autopurge.snapRetainCount=3
autopurge.purgeInterval=24
# 集群成员
server.1=10.0.0.21:2888:3888
server.2=10.0.0.22:2888:3888
server.3=10.0.0.23:2888:3888bash
mkdir -p /data/zookeeper
echo 1 > /data/zookeeper/myid # 10.0.0.22 写 2,10.0.0.23 写 3| 参数 | 含义 | 调错的后果 |
|---|---|---|
tickTime | ZK 的基本时间单位(ms) | 一般不用改 |
initLimit | follower 与 leader 初始同步允许的 tick 数 | 数据量大时同步超时,选举失败 |
syncLimit | 心跳超时 tick 数 | 太小会频繁误判 follower 挂 |
2888 | follower 与 leader 数据同步 | 必须放通 |
3888 | 选举通信 | 必须放通 |
autopurge.purgeInterval | 几小时清理一次 | 不设 → 磁盘被快照吃满 |
systemd:
ini
# /etc/systemd/system/zookeeper.service
[Unit]
Description=Apache Zookeeper
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=kafka
Group=kafka
Environment="JAVA_HOME=/usr/local/jdk-11"
ExecStart=/opt/kafka/bin/zookeeper-server-start.sh /opt/kafka/config/zookeeper.properties
ExecStop=/opt/kafka/bin/zookeeper-server-stop.sh
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
[Install]
WantedBy=multi-user.targetbash
useradd -r -s /sbin/nologin kafka
chown -R kafka:kafka /data/zookeeper /opt/kafka
systemctl daemon-reload
systemctl enable --now zookeeper验证(三台依次起完再验):
bash
echo ruok | nc 10.0.0.21 2181
echo stat | nc 10.0.0.21 2181
echo mntr | nc 10.0.0.21 2181 | grep -E 'zk_server_state|zk_followers|zk_synced_followers'text
imok
Zookeeper version: 3.8.4-9316c2a1d5b4d1c0b3c8b3f6d3d3d6c1e2f3a4b5, built on 2024-02-09
Clients:
/10.0.0.21:51234[1](queued=0,recved=1,sent=0)
Latency min/avg/max: 0/0.0/0
Received: 1
Sent: 0
Connections: 1
Outstanding: 0
Zxid: 0x0
Mode: standalone
Node count: 5
zk_server_state leader
zk_followers 2
zk_synced_followers 2⚠️ 关键:zk_server_state 必须是 leader 或 follower。如果显示 standalone 或 looking:
standalone:server.N没配对,或myid缺失/K 写错 → 它以为自己是单机。looking:正在选举,长时间不结束说明三台互相连不上(检查 2888/3888)。
三台都在 looking 是最常见的部署失败
原因几乎都是:myid 内容不对(写成了 server.1 或带了换行以外字符)、server.N 的 IP 填错、2888/3888 被防火墙挡。先 ss -lntp | grep -E '2888|3888' 确认在监听,再用 nc -vz 逐个测连通。
四、broker 配置
三台的 server.properties 只有 broker.id、listeners、advertised.listeners 三处不同。
ini
# /opt/kafka/config/server.properties
broker.id=1
listeners=PLAINTEXT://10.0.0.21:9092
advertised.listeners=PLAINTEXT://10.0.0.21:9092
num.network.threads=8
num.io.threads=16
socket.send.buffer.bytes=1048576
socket.receive.buffer.bytes=1048576
socket.request.max.bytes=104857600
log.dirs=/data/kafka/logs
num.partitions=3
num.recovery.threads.per.data.dir=2
# 副本与 ISR
default.replication.factor=2
min.insync.replicas=2
unclean.leader.election.enable=false
# 内部 topic 必须 3 副本,否则三台死一台就报错
offsets.topic.replication.factor=3
transaction.state.log.replication.factor=3
transaction.state.log.min.isr=2
# 日志保留
log.retention.hours=168
log.segment.bytes=1073741824
log.retention.check.interval.ms=300000
# 禁止自动建 topic(生产必须关)
auto.create.topics.enable=false
delete.topic.enable=true
zookeeper.connect=10.0.0.21:2181,10.0.0.22:2181,10.0.0.23:2181/kafka
zookeeper.connection.timeout.ms=18000逐项解释:
| 配置 | 为什么这么设 |
|---|---|
advertised.listeners | 客户端实际连的地址。设错就是"本机 --bootstrap-server localhost 能用,别的机器连不上" |
log.dirs | 单独一块盘,别和系统盘共用(日志写满会带崩 OS) |
offsets.topic.replication.factor=3 | 默认是 3 但只在集群有 3 台 broker 时才能真正建成;集群只有 1 台时这个 topic 建不出来,消费组用不了 |
unclean.leader.election.enable=false | 宁可不可用也不选一个落后副本当 leader(会丢数据) |
auto.create.topics.enable=false | 开着的话,生产者打错 topic 名就静默创建一堆 1 分区 topic |
zookeeper.connect 末尾的 /kafka | 所有元数据放 /kafka 下,同一套 ZK 可以给多个 Kafka 集群用 |
default.replication.factor 不等于自动建 topic 的副本数
真正生效的是 default.replication.factor(3.0+)或 offsets.topic.replication.factor 管内部 topic、num.partitions + default.replication.factor 管自动建的业务 topic。生产环境 auto.create.topics.enable=false,所有 topic 都该显式创建并指定副本数。
五、启动与验证
bash
chown -R kafka:kafka /data/kafka
systemctl enable --now kafka
journalctl -u kafka -n 30 --no-pager正常日志:
text
[2026-10-27 19:10:02,114] INFO [KafkaServer id=1] started (kafka.server.KafkaServer)
[2026-10-27 19:10:02,188] INFO [BrokerLifecycleManager id=1] Registered broker 1 with metadata.version 3.9-IV0. (kafka.server.BrokerLifecycleManager)集群检查:
bash
/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server 10.0.0.21:9092 | head -5
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 10.0.0.21:9092 --listtext
10.0.0.21:9092 (id: 1 rack: null) -> (
Produce(0): 0 to 11 [usable: 11],
Fetch(1): 0 to 13 [usable: 13],
...
)
__consumer_offsets__consumer_offsets 已经存在,说明内部 topic 建成功了。
确认三台都注册到 ZK:
bash
/opt/kafka/bin/zookeeper-shell.sh 10.0.0.21:2181 ls /kafka/brokers/idstext
Connecting to 10.0.0.21:2181
Welcome to ZooKeeper!
JLine support is enabled
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
[1, 2, 3][1, 2, 3] 就是三台 broker 的 broker.id。少一个,去那台看日志。
六、冒烟测试
bash
# 建 topic:3 分区 2 副本
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 10.0.0.21:9092 \
--create --topic smoke --partitions 3 --replication-factor 2
# 看分布
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 10.0.0.21:9092 --describe --topic smoketext
Created topic smoke.
Topic: smoke TopicId: 8sT2mQ1nR4vW6xY8zA0bCw PartitionCount: 3 ReplicationFactor: 2 Configs:
Topic: smoke Partition: 0 Leader: 1 Replicas: 1,2 Isr: 1,2
Topic: smoke Partition: 1 Leader: 2 Replicas: 2,3 Isr: 2,3
Topic: smoke Partition: 2 Leader: 3 Replicas: 3,1 Isr: 3,1Isr 与 Replicas 一样长 = 全部同步中。如果 Isr: 1(只有 leader),说明副本没跟上,看 kafka-server.log 里的副本拉取错误。
bash
# 生产
printf 'hello\nworld\n' | /opt/kafka/bin/kafka-console-producer.sh \
--bootstrap-server 10.0.0.21:9092 --topic smoke
# 消费
/opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server 10.0.0.21:9092 \
--topic smoke --from-beginning --max-messages 2text
hello
world
Processed a total of 2 messages七、常用运维
bash
# 停
systemctl stop kafka
# 滚动重启(一次一台,等它 ISR 恢复再下一台)
for h in 10.0.0.21 10.0.0.22 10.0.0.23; do
ssh $h 'systemctl restart kafka'
sleep 30
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 10.0.0.21:9092 --describe --under-replicated-partitions
done
# 看 controller 是谁
/opt/kafka/bin/zookeeper-shell.sh 10.0.0.21:2181 get /kafka/controllertext
{"version":1,"brokerid":2,"timestamp":"1761549602114"}brokerid 就是当前 controller。controller 负责分区 leader 选举、副本分配,它挂了会触发重新选举(通常几秒)。
八、踩过的坑
| 现象 | 根因 | 处置 |
|---|---|---|
ZK 三台都 Mode: standalone | server.N/myid 没用上 | 检查 myid 内容与路径 |
ZK 长期 looking | 2888/3888 不通 | 放通端口,nc -vz 验证 |
| 本机能连、别的机器连不上 | advertised.listeners 填了 localhost | 改成真实 IP |
消费组报 __consumer_offsets 相关错误 | 集群不足 3 台时该 topic 建不出来 | 临时把 offsets.topic.replication.factor 调到 broker 数,或补齐 broker |
broker.id 重复 | 拷贝配置没改 | 每台唯一,且不要随意改(改了集群不认) |
重启后数据目录报 Cluster ID mismatch | log.dirs 被换过/挂载丢失 | 恢复原目录,别用空目录启动 |
| 磁盘不明原因增长 | 没开 log.retention.bytes,或 autopurge 关着 | 见 Topic 与分区 |
集群只有 1 台 broker 却设 RF=3 | 创建 topic 直接失败 replication factor: 3 larger than available brokers: 1 | 先凑够 broker 或降 RF |