ZooKeeper + Kafka 集群搭建实战记录

环境:Rocky Linux 9.4 / ZooKeeper 3.8.6 / Kafka 2.13-3.9.2

目标:单机伪集群(3个ZK节点 + 3个Kafka Broker),用于学习分布式消息队列原理


一、环境说明

服务器信息

项目 信息
操作系统 Rocky Linux 9.4 (Blue Onyx)
内网 IP 172.17.0.7
公网 IP 124.221.242.29
ZooKeeper 版本 3.8.6
Kafka 版本 kafka_2.13-3.9.2

初始目录结构

登录服务器后 /opt 目录下已有以下文件:

复制代码
/opt/
├── apache-zookeeper-3.8.6-bin.tar.gz   ← ZooKeeper 安装包
├── kafka_2.13-3.9.2.tgz                ← Kafka 安装包
├── zookeeper/                          ← 已解压的 ZooKeeper
└── kafka/                              ← 已解压的 Kafka

二、为什么要搭集群?核心原理

ZooKeeper 集群原理

ZooKeeper 的核心职责是做分布式协调(比如帮 Kafka 选举 Leader、存储元数据)。如果 ZooKeeper 自己是单点,一旦挂掉整个依赖它的系统就全崩了,所以 ZooKeeper 自身也要高可用。

解决方案就是多个 ZK 节点互相备份,称为 ZooKeeper 集群(Ensemble)

复制代码
        ┌─────────────────────────────────────┐
        │          ZooKeeper 集群             
        │                                     
        │  [ZK1-Leader] ←→ [ZK2] ←→ [ZK3]     
        │      数据同步      数据同步           
        └─────────────────────────────────────┘
               ↑ 任意一个挂掉,集群照常运行

关键规则:超过半数节点存活才能正常工作。

  • 3个节点:挂1个还剩2个 > 1.5
  • 2个节点:挂1个只剩1个 = 1.0

这也是为什么集群节点数必须是奇数,最少3个。

伪集群 vs 真实集群

由于只有一台服务器,无法搭建真正的多机集群,采用伪集群方式:

复制代码
真实多机集群(3台服务器):         伪集群(1台服务器):

服务器A  →  ZK进程               /opt/zookeeper/zk1/  →  ZK进程1(端口2181)
服务器B  →  ZK进程               /opt/zookeeper/zk2/  →  ZK进程2(端口2182)
服务器C  →  ZK进程               /opt/zookeeper/zk3/  →  ZK进程3(端口2183)

每台服务器天然隔离               用文件夹 + 不同端口来隔离

伪集群的本质:用不同目录 + 不同端口,在同一台机器上模拟多台服务器。


三、搭建 ZooKeeper 伪集群

3.1 目录结构规划

复制代码
/opt/zookeeper/
├── bin/              ← ZK 启动脚本(3个节点共用同一套程序)
├── lib/              ← ZK 的 jar 包
├── conf/zoo.cfg      ← 原始默认配置(单机用,集群不用这个)
│
├── zk1/              ← 模拟节点1(相当于第1台服务器)
│   ├── zoo.cfg       ← 节点1的配置(clientPort=2181)
│   ├── data/
│   │   └── myid      ← 内容: 1
│   └── logs/
│
├── zk2/              ← 模拟节点2
│   ├── zoo.cfg       ← 节点2的配置(clientPort=2182)
│   ├── data/
│   │   └── myid      ← 内容: 2
│   └── logs/
│
└── zk3/              ← 模拟节点3
    ├── zoo.cfg       ← 节点3的配置(clientPort=2183)
    ├── data/
    │   └── myid      ← 内容: 3
    └── logs/

3.2 创建目录并写入 myid

复制代码
# 创建3个节点各自的数据目录和日志目录
mkdir -p /opt/zookeeper/zk1/data /opt/zookeeper/zk1/logs
mkdir -p /opt/zookeeper/zk2/data /opt/zookeeper/zk2/logs
mkdir -p /opt/zookeeper/zk3/data /opt/zookeeper/zk3/logs

# 写入 myid(每个节点的唯一标识)
echo 1 > /opt/zookeeper/zk1/data/myid
echo 2 > /opt/zookeeper/zk2/data/myid
echo 3 > /opt/zookeeper/zk3/data/myid

关于 myid 的说明:

  • 文件名 myid固定的 ,ZooKeeper 启动时会自动去 dataDir 目录下找这个文件

  • 里面的数字是自定义的 ,但必须是正整数,且和配置文件里的 server.X 的 X 对应

  • 集群内不能重复

    myid 文件内容 对应配置文件中的
    1 ←→ server.1=172.17.0.7:2888:3888
    2 ←→ server.2=172.17.0.7:2889:3889
    3 ←→ server.3=172.17.0.7:2890:3890

3.3 创建配置文件

将原始配置复制到三个节点目录,再分别修改:

复制代码
cp /opt/zookeeper/conf/zoo.cfg /opt/zookeeper/zk1/zoo.cfg
cp /opt/zookeeper/conf/zoo.cfg /opt/zookeeper/zk2/zoo.cfg
cp /opt/zookeeper/conf/zoo.cfg /opt/zookeeper/zk3/zoo.cfg

zk1/zoo.cfg:

复制代码
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/opt/zookeeper/zk1/data        # 节点1数据目录
dataLogDir=/opt/zookeeper/zk1/logs     # 节点1日志目录
clientPort=2181                         # 客户端连接端口(各节点不同)

# 集群成员列表(三个节点都要写,且完全一样)
server.1=172.17.0.7:2888:3888
server.2=172.17.0.7:2889:3889
server.3=172.17.0.7:2890:3890

zk2/zoo.cfg:(只有3处不同)

复制代码
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/opt/zookeeper/zk2/data        # ← 改成 zk2
dataLogDir=/opt/zookeeper/zk2/logs     # ← 改成 zk2
clientPort=2182                         # ← 改成 2182

server.1=172.17.0.7:2888:3888
server.2=172.17.0.7:2889:3889
server.3=172.17.0.7:2890:3890

zk3/zoo.cfg:(同理)

复制代码
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/opt/zookeeper/zk3/data        # ← 改成 zk3
dataLogDir=/opt/zookeeper/zk3/logs     # ← 改成 zk3
clientPort=2183                         # ← 改成 2183

server.1=172.17.0.7:2888:3888
server.2=172.17.0.7:2889:3889
server.3=172.17.0.7:2890:3890

三个节点配置文件差异对比:

配置项 zk1 zk2 zk3
dataDir zk1/data zk2/data zk3/data
dataLogDir zk1/logs zk2/logs zk3/logs
clientPort 2181 2182 2183
server.x 完全相同 完全相同 完全相同

3.4 端口说明

配置文件中涉及3种端口,含义完全不同:

复制代码
clientPort=2181          # 客户端连接端口(Kafka 连 ZK 用这个)

server.1=172.17.0.7:2888:3888
#                   ↑    ↑
#           节点间数据同步  Leader 选举投票
端口类型 端口号 用途 谁来连
clientPort 2181/2182/2183 客户端通信 Kafka、客户端程序
数据同步端口 2888/2889/2890 Follower 同步数据给 Leader ZK 节点之间
选举端口 3888/3889/3890 Leader 选举投票 ZK 节点之间

3.5 启动 ZooKeeper 集群

复制代码
zkServer.sh start /opt/zookeeper/zk1/zoo.cfg
zkServer.sh start /opt/zookeeper/zk2/zoo.cfg
zkServer.sh start /opt/zookeeper/zk3/zoo.cfg

注意: 启动时必须指定配置文件路径! zkServer.sh start 不指定路径的话,3个节点会都读默认的 conf/zoo.cfg,集群就乱了。

zkServer.sh 常用命令:

命令 作用
start [cfg] 后台启动
stop [cfg] 停止
restart [cfg] 重启
status [cfg] 查看节点角色(leader/follower)
start-foreground [cfg] 前台启动,报错时用于调试

3.6 验证 ZooKeeper 集群状态

复制代码
zkServer.sh status /opt/zookeeper/zk1/zoo.cfg
zkServer.sh status /opt/zookeeper/zk2/zoo.cfg
zkServer.sh status /opt/zookeeper/zk3/zoo.cfg

正常输出(1个 leader + 2个 follower):

复制代码
Mode: leader
Mode: follower
Mode: follower

3.7 ZooKeeper 启动过程原理

复制代码
启动 zk1 进程
  └─ 读取 zk1/zoo.cfg
  └─ 读取 zk1/data/myid → 得知"我是节点1"
  └─ 监听 clientPort=2181
  └─ 向 server.2、server.3 发起连接,等待集群组建

启动 zk2 进程
  └─ 同上,得知"我是节点2"
  └─ 监听 clientPort=2182
  └─ 和节点1握手成功

启动 zk3 进程
  └─ 同上,得知"我是节点3"
  └─ 三个节点互相连通 → 开始 Leader 选举
  └─ 选出一个 Leader,其余成为 Follower → 集群就绪!

四、搭建 Kafka 伪集群

4.1 目录结构规划

复制代码
/opt/kafka/
├── bin/                  ← Kafka 启动脚本
├── config/
│   └── server.properties ← 原始默认配置(单机用)
│
├── broker1/
│   ├── server.properties ← broker1 配置(port=9092, id=1)
│   └── logs/             ← broker1 数据目录
│
├── broker2/
│   ├── server.properties ← broker2 配置(port=9093, id=2)
│   └── logs/             ← broker2 数据目录
│
└── broker3/
    ├── server.properties ← broker3 配置(port=9094, id=3)
    └── logs/             ← broker3 数据目录

4.2 创建目录并复制配置

复制代码
# 创建数据目录
mkdir -p /opt/kafka/broker1/logs
mkdir -p /opt/kafka/broker2/logs
mkdir -p /opt/kafka/broker3/logs

# 从默认配置复制
cp /opt/kafka/config/server.properties /opt/kafka/broker1/server.properties
cp /opt/kafka/config/server.properties /opt/kafka/broker2/server.properties
cp /opt/kafka/config/server.properties /opt/kafka/broker3/server.properties

4.3 修改配置文件

broker1/server.properties:

需要修改的项(原文件里有,找到改掉):

复制代码
broker.id=1
listeners=PLAINTEXT://172.17.0.7:9092
advertised.listeners=PLAINTEXT://172.17.0.7:9092
log.dirs=/opt/kafka/broker1/logs
zookeeper.connect=172.17.0.7:2181,172.17.0.7:2182,172.17.0.7:2183

需要在文件末尾追加的项(原文件没有):

复制代码
num.partitions=3
default.replication.factor=3
min.insync.replicas=2
offsets.topic.replication.factor=3

broker2/server.properties:(只改 id 和端口)

复制代码
broker.id=2                                        # ← 改成 2
listeners=PLAINTEXT://172.17.0.7:9093              # ← 改成 9093
advertised.listeners=PLAINTEXT://172.17.0.7:9093   # ← 改成 9093
log.dirs=/opt/kafka/broker2/logs                   # ← 改成 broker2
zookeeper.connect=172.17.0.7:2181,172.17.0.7:2182,172.17.0.7:2183

# 末尾追加(同 broker1)
num.partitions=3
default.replication.factor=3
min.insync.replicas=2
offsets.topic.replication.factor=3

broker3/server.properties:

复制代码
broker.id=3                                        # ← 改成 3
listeners=PLAINTEXT://172.17.0.7:9094              # ← 改成 9094
advertised.listeners=PLAINTEXT://172.17.0.7:9094   # ← 改成 9094
log.dirs=/opt/kafka/broker3/logs                   # ← 改成 broker3
zookeeper.connect=172.17.0.7:2181,172.17.0.7:2182,172.17.0.7:2183

# 末尾追加(同 broker1)
num.partitions=3
default.replication.factor=3
min.insync.replicas=2
offsets.topic.replication.factor=3

新增配置项说明:

配置项 含义 为什么设3
num.partitions 新建 Topic 默认分区数 对应3个broker,负载均衡
default.replication.factor 默认副本数 3个broker各存一份,任意挂一个不丢数据
min.insync.replicas 最少同步副本数 至少2个副本写成功才算消息可靠
offsets.topic.replication.factor 消费位移 Topic 的副本数 同上,保证可靠性

三个 broker 配置差异对比:

配置项 broker1 broker2 broker3
broker.id 1 2 3
listeners 端口 9092 9093 9094
log.dirs broker1/logs broker2/logs broker3/logs
zookeeper.connect 完全相同 完全相同 完全相同

4.4 启动 Kafka 集群

复制代码
kafka-server-start.sh -daemon /opt/kafka/broker1/server.properties
sleep 2
kafka-server-start.sh -daemon /opt/kafka/broker2/server.properties
sleep 2
kafka-server-start.sh -daemon /opt/kafka/broker3/server.properties
sleep 3

# 验证进程
jps

正常输出(3个 ZK + 3个 Kafka):

复制代码
xxxxxx QuorumPeerMain   ← ZooKeeper节点1
xxxxxx QuorumPeerMain   ← ZooKeeper节点2
xxxxxx QuorumPeerMain   ← ZooKeeper节点3
xxxxxx Kafka            ← Kafka broker1
xxxxxx Kafka            ← Kafka broker2
xxxxxx Kafka            ← Kafka broker3

五、遇到的问题与解决过程

问题1:ZooKeeper 已有解压目录,不需要重新解压

现象: 服务器 /opt 下同时存在 .tar.gz 压缩包和已解压的目录。

原因: 之前已经手动解压过一次,目录名直接叫 zookeeperkafka

解决: 直接使用已解压的目录即可,不需要重复解压。


问题2:原始 conf/zoo.cfg 要不要处理

疑问: /opt/zookeeper/conf/zoo.cfg 是单机默认配置,搭集群要不要删掉或修改它?

结论: 不用管它,因为启动集群时明确指定了配置文件路径:

复制代码
zkServer.sh start /opt/zookeeper/zk1/zoo.cfg   # 指定 zk1 的配置

ZooKeeper 只会读指定的配置文件,不会去读默认的 conf/zoo.cfg,两者互不干扰。


问题3:Kafka 只启动了1个 broker,另外2个没启动成功

现象: 执行三条启动命令后,jps 只看到1个 Kafka 进程:

复制代码
2563839 Kafka   ← 只有1个,应该是3个

排查过程:

复制代码
# 查看 broker2 和 broker3 的日志目录,发现是空的(连 server.log 都没有)
ls /opt/kafka/broker2/logs   # 空
ls /opt/kafka/broker3/logs   # 空

日志目录是空的,说明进程启动瞬间就崩溃了,没来得及写日志。

用前台模式启动 broker2 查看报错:

复制代码
kafka-server-start.sh /opt/kafka/broker2/server.properties
# 不加 -daemon,错误日志直接打印在终端

报错信息:

复制代码
Stored node id 1 doesn't match previous node id 2

根本原因:

  • broker1 第一次成功启动后,Kafka 把 broker.id=1 写入了 broker1/logs/meta.properties
  • 之后启动 broker2 时,log.dirs 配置错误,指向了已经被 broker1 用过的目录
  • Kafka 发现目录里记录的 id=1,但配置文件写的 id=2,拒绝启动

实质上是:三个 broker 的 log.dirs 配置重复指向了同一个目录。

解决方法:

复制代码
# 清空所有broker的日志目录(删除残留的 meta.properties)
rm -rf /opt/kafka/broker1/logs/*
rm -rf /opt/kafka/broker2/logs/*
rm -rf /opt/kafka/broker3/logs/*

# 停掉当前运行的 Kafka
kafka-server-stop.sh
sleep 3

# 确认配置文件中 log.dirs 各不相同后,重新启动
kafka-server-start.sh -daemon /opt/kafka/broker1/server.properties
sleep 2
kafka-server-start.sh -daemon /opt/kafka/broker2/server.properties
sleep 2
kafka-server-start.sh -daemon /opt/kafka/broker3/server.properties

经验总结:

只要看到报错 Stored node id X doesn't match previous node id Y, 说明数据目录里残留了旧的 broker.id 记录,与当前配置冲突。 解决方法:清空对应的 logs 目录,重新启动。

meta.properties 文件内容示例:

复制代码
# 由 Kafka 自动生成,不要手动修改
broker.id=1
version=0

六、集群验证

6.1 查看 ZooKeeper 中注册的 broker

复制代码
# 连接 ZooKeeper CLI
zkCli.sh -server 172.17.0.7:2181

# 查看已注册的 broker id 列表
ls /brokers/ids
# 正常输出:[1, 2, 3]

# 查看某个 broker 的详细信息
get /brokers/ids/1

6.2 创建测试 Topic

复制代码
kafka-topics.sh \
  --create \
  --bootstrap-server 172.17.0.7:9092 \
  --topic test-topic \
  --partitions 3 \
  --replication-factor 3

# 查看 Topic 详情(重点关注 Leader 分布和 ISR 列表)
kafka-topics.sh \
  --describe \
  --bootstrap-server 172.17.0.7:9092 \
  --topic test-topic

正常输出示例:

复制代码
Topic: test-topic  Partition: 0  Leader: 2  Replicas: 2,1,3  Isr: 2,1,3
Topic: test-topic  Partition: 1  Leader: 3  Replicas: 3,2,1  Isr: 3,2,1
Topic: test-topic  Partition: 2  Leader: 1  Replicas: 1,3,2  Isr: 1,3,2

每个分区都有独立的 Leader,且 ISR 列表包含全部3个 broker,说明集群完全正常。

6.3 生产者消费者测试

复制代码
# 终端1:启动生产者
kafka-console-producer.sh \
  --bootstrap-server 172.17.0.7:9092 \
  --topic test-topic

# 终端2:启动消费者
kafka-console-consumer.sh \
  --bootstrap-server 172.17.0.7:9092 \
  --topic test-topic \
  --from-beginning

七、快速启停脚本

启动脚本 /opt/cluster-start.sh

复制代码
#!/bin/bash
ZK=/opt/zookeeper/bin/zkServer.sh
KF=/opt/kafka/bin/kafka-server-start.sh

echo "=== 启动 ZooKeeper 集群 ==="
zkServer.sh start /opt/zookeeper/zk1/zoo.cfg
zkServer.sh start /opt/zookeeper/zk2/zoo.cfg
zkServer.sh start /opt/zookeeper/zk3/zoo.cfg
sleep 5

echo "=== 启动 Kafka 集群 ==="
kafka-server-start.sh -daemon /opt/kafka/broker1/server.properties
sleep 2
kafka-server-start.sh -daemon /opt/kafka/broker2/server.properties
sleep 2
kafka-server-start.sh -daemon /opt/kafka/broker3/server.properties
sleep 3

echo "=== 集群状态 ==="
jps

停止脚本 /opt/cluster-stop.sh

复制代码
#!/bin/bash
echo "=== 停止 Kafka 集群 ==="
kafka-server-stop.sh
sleep 3

echo "=== 停止 ZooKeeper 集群 ==="
zkServer.sh stop /opt/zookeeper/zk1/zoo.cfg
zkServer.sh stop /opt/zookeeper/zk2/zoo.cfg
zkServer.sh stop /opt/zookeeper/zk3/zoo.cfg

echo "=== 当前进程 ==="
jps

# 赋予执行权限
chmod +x /opt/cluster-start.sh /opt/cluster-stop.sh

八、常用运维命令速查

ZooKeeper 相关

复制代码
# 启动/停止/重启/查看状态
zkServer.sh start|stop|restart|status /opt/zookeeper/zkX/zoo.cfg

# 连接 ZK CLI
zkCli.sh -server 172.17.0.7:2181

# ZK CLI 常用操作
ls /                        # 列出根节点
ls /brokers/ids             # 查看 Kafka broker 列表
get /brokers/ids/1          # 查看 broker 详情
get /controller             # 查看当前 Kafka Controller
ls /brokers/topics          # 查看所有 topic

Kafka 相关

复制代码
# 启动(后台)/ 停止
kafka-server-start.sh -daemon <配置文件>
kafka-server-stop.sh

# Topic 管理
kafka-topics.sh --create --bootstrap-server 172.17.0.7:9092 --topic <名称> --partitions 3 --replication-factor 3
kafka-topics.sh --list --bootstrap-server 172.17.0.7:9092
kafka-topics.sh --describe --bootstrap-server 172.17.0.7:9092 --topic <名称>
kafka-topics.sh --delete --bootstrap-server 172.17.0.7:9092 --topic <名称>

# 生产者 / 消费者
kafka-console-producer.sh --bootstrap-server 172.17.0.7:9092 --topic <名称>
kafka-console-consumer.sh --bootstrap-server 172.17.0.7:9092 --topic <名称> --from-beginning

# 查看消费者组
kafka-consumer-groups.sh --bootstrap-server 172.17.0.7:9092 --list
kafka-consumer-groups.sh --bootstrap-server 172.17.0.7:9092 --describe --group <组名>

九、下一步学习建议

阶段一:理解核心概念

  • Partition(分区):Topic 的并行处理基础,同一分区内消息有序
  • Replica(副本):Leader + Follower 副本机制,保证高可用
  • ISR(In-Sync Replicas):正在同步的副本集合,ISR 缩减说明有副本落后
  • Offset(位移):消费者记录自己消费到哪条消息的标记

阶段二:实验 - 模拟故障转移

复制代码
# 1. 查看当前 Controller 是哪个 broker
zkCli.sh -server 172.17.0.7:2181 get /controller

# 2. 杀掉这个 broker
kill <对应的Kafka进程PID>

# 3. 立刻再查,观察新 Controller 产生
zkCli.sh -server 172.17.0.7:2181 get /controller

# 4. 查看 Topic 的 Leader 是否重新分配
kafka-topics.sh --describe --bootstrap-server 172.17.0.7:9092 --topic test-topic

阶段三:理解 ZooKeeper 在 Kafka 中的角色

ZooKeeper 存储了 Kafka 的所有关键元数据:

复制代码
/brokers/ids/       ← broker 注册信息
/brokers/topics/    ← topic 分区信息
/controller         ← 当前 Controller broker
/consumers/         ← 消费者组信息(旧版)
/config/topics/     ← topic 配置
/isr_change_notification/  ← ISR 变更通知

最后更新:2026年5月

相关推荐
星轨zb6 小时前
JUC 到 Redis 分布式锁:一次关于高并发的性能压测实验
java·redis·分布式·jmeter
心中有国也有家6 小时前
PaddlePaddle 适配 NPU 的技术全解析——从算子接入到端到端性能优化
人工智能·分布式·算法·性能优化·架构·paddlepaddle
郑小憨7 小时前
zookeeper内部原理 (进阶介绍 三)
大数据·分布式·zookeeper
厌灵泽(后端小白)7 小时前
Windows11本地安装Zookeeper(最新)
大数据·windows·zookeeper·笔记本电脑
java1234_小锋7 小时前
【吊打面试官系列-ZooKeeper面试题】zookeeper 是如何保证事务的顺序一致性的?
分布式·zookeeper·云原生
小江的记录本7 小时前
【Kafka核心】Kafka 3.0+ KRaft模式(替代ZooKeeper)核心原理与优势
java·数据库·分布式·后端·zookeeper·kafka·rabbitmq
bing_1587 小时前
Zookeeper 在 Kafka 中扮演了什么角色?
分布式·zookeeper·kafka
醉颜凉7 小时前
Kafka为什么抛弃ZooKeeper?深度解析KRaft时代的技术变革
zookeeper·kafka·linq
my19587021357 小时前
ZooKeeper分布式协调从入门到实战
分布式·zookeeper·云原生