Kafka与ZooKeeper集群搭建实战:从0到1的完整指南
2026/9/10 5:14:23 网站建设 项目流程

如果你手头正好有几台空闲的服务器,领导丢给你一句“把Kafka集群搭起来”,然后就没有下文了,那你现在的心情我特别懂。我在这种场景下踩过的坑,比看文档时以为的多一倍:版本之间的坑、ZK和Kafka互相依赖的坑、配置文件里一个参数没对齐导致的连锁故障……这篇就把从0到1搭建Kafka和ZooKeeper集群的路完整走一遍。

这是一篇面向实操的记录,适合已经有Linux基础、对消息队列有概念但没真正部署过的人。跟着这篇文章走完,你得到的是一套三节点集群:三台ZooKeeper、三台Kafka,能扛单机故障,能正常生产消费,并且留好了后续监控和扩容的入口。我会把“为什么这么做”也讲清楚,而不只是甩给你一份能跑的配置。每台机器什么角色、每个参数什么含义、启动之后怎么验证,都会落到具体命令和输出上,方便你照着抄,也方便出问题时回头查。

1. 搭建前先理清架构:Kafka和ZooKeeper是怎么配合的

1.1 各管一摊:元数据与消息数据的分工

先说一个很多人最初理解偏了的地方:Kafka和ZooKeeper不是“一套软件装一起”这么简单的关系。Kafka的Broker节点本身专注做消息存储和读写,消息落盘、副本复制、消费组拉取,这些事情都由Broker自己处理。但分布式系统最麻烦的问题是“谁说了算”——哪台Broker是Controller、某个分区的Leader副本在哪台机器上、某个消费组现在消费到哪个offset,这些元数据必须有个大家都能访问的协调者,ZooKeeper就是干这个的。

打个比方,Kafka的Broker像是市场里的一个个摊位,ZooKeeper就是市场管理处。摊主出摊了要去管理处报到,撤摊了要注销;摊位之间起了纠纷也去管理处协调。如果管理处挂了,买卖双方都会心里没底,整个市场虽然还摆着摊,但已经乱了套。所以在老版本架构下,ZooKeeper不是可选项,而是Kafka集群的基础设施。

这里有一个关键点:ZooKeeper只负责协调和元数据,不碰消息数据本身。消息数据永远在Kafka Broker的本地磁盘上。理解这个分工,你后面排查问题就会有一个方向感:当“消费者找不到分区”“Broker注册不上”这类问题时,往ZK这边想;当“消息写不进去”“磁盘IO打满”时,往Broker这边想。两者之间有明确的边界,不要一上来就全盘怀疑。

1.2 集群规模:为什么要奇数台

ZooKeeper集群的规模不是随便拍的。它采用Zab协议做数据一致性,写入一条元数据要得到过半节点确认才算成功。所谓过半就是“n/2 + 1”这个概念:3台机器要2台确认,5台要3台确认。因此奇数台能在“可用性”和“成本”之间取得平衡——2台和1台效果一样,4台和3台容错能力一样,但多花了钱。

所以ZK的常见规模就是1、3、5、7。生产环境我建议至少3台,如果条件允许,单独用3台机器装ZK,不要和Kafka Broker混用。混装在节约机器的时候能跑,但ZK和Kafka都是对资源有要求的服务,尤其是IO和内存,混装容易互相踩脚。Kafka的Broker数量则按业务流量来算,和ZK的数量没有必须相等的关系,三节点起步主要为了让副本机制能真正发挥作用——如果只有1台Kafka,副本因子配3也没有意义。

1.3 版本选型:不要一上来就追新

版本这块是新手最容易踩坑的地方。Kafka从3.3开始逐步引入KRaft模式,可以脱离ZooKeeper运行,但直到3.5.x,ZK模式依然是存量企业用得最多的生产方式,相关资料也最全。你在生产环境遇到问题要快速查资料、找解决方案时,用稳定且资料多的版本一定更舒服。

我这次选的是Kafka 3.3.2加上ZooKeeper 3.7.1,配OpenJDK 11。这个组合是我在生产上验证过的,稳定,坑少。Kafka 3.x的官方包里其实自带一个ZooKeeper,但那个ZK主要是给快速体验用,集群模式建议单独下载ZooKeeper发行版,这样版本可控,出问题也好定位。选版本还有一个原则:Kafka和ZK的版本兼容性以官方Release Notes为准,不要拿最新版ZK去配旧版Kafka,否则可能出现序列化协议不匹配之类的怪问题。

2. 环境准备:从JDK到系统参数的完整核对清单

2.1 JDK版本对齐

装之前先把三台机器的JDK统一好,这个步骤很多人跳过去,后面就吃大亏。Kafka 3.3.x官方支持Java 8、11、17,我推荐用11;ZooKeeper 3.7.x要求Java 8以上。三台机器如果JDK版本不一致,轻则某个节点起不来,重则集群出现奇怪的时间同步和序列化问题,排查起来非常头疼。

安装方式不用太纠结,OpenJDK直接解压或者用包管理器装都行,关键是配好JAVA_HOME。我习惯把JDK放在/opt/jdk目录,然后写进/etc/profile.d/java.sh,确保所有用户都能读到。装完之后依次执行java -version和echo $JAVA_HOME,确认三台输出完全一致再往下走。这一步别嫌麻烦,集群环境最怕“看起来一样,实际有差异”的隐性问题。

2.2 文件句柄、swap和时间同步

这三个系统参数,是我在集群出过问题之后才真正重视起来的。Kafka数据目录下会生成大量segment文件,每个文件都要占一个文件句柄,生产环境并发起来,默认的1024根本不够。需要修改/etc/security/limits.conf,给运行用户设置nofile和nproc,比如nofile设为65535或更高,改完要重新登录会话才生效。很多人改完limits发现不生效,就是因为没有重新登录。

swap的问题在于Linux内核默认会把不常用的内存页换到磁盘,但Kafka这种低延迟系统最怕换页抖动,一旦发生swap,消息写入延迟会突然飙高。把vm.swappiness调成1,核心业务期间基本不会主动换页。时间同步同样重要,Kafka日志时间戳和副本复制重试都依赖机器时间,三台机器时间偏差太大会出现日志错乱,务必在每台机器上跑通ntp或chrony,并且确认用的时间源一致。

2.3 数据目录规划与多磁盘挂载

数据目录的规划决定了集群能跑多久不出磁盘问题。ZooKeeper的数据量不大,但它的事务日志对写延迟很敏感,强烈建议把dataDir和dataLogDir分开,dataLogDir放独立的物理盘或者SSD上。Kafka的日志目录更讲究,如果机器上有两块以上数据盘,可以用逗号把多个路径配给log.dirs,让Kafka自动做跨目录负载均衡,而不是所有分区都压在系统盘上。

我习惯的布局是:系统盘放程序,数据盘挂到/data下,ZooKeeper用/data/zookeeper/data和/data/zookeeper/logs,Kafka用/data/kafka/logs。挂载的时候给上noatime参数,减少不必要的磁盘写操作。这一步看着小,但对集群长期性能影响很大,尤其是Broker数量上去了以后,磁盘布局不合理的机器会最先暴露瓶颈。

3. 手把手搭ZooKeeper集群:3台机器的完整过程

3.1 下载解压与基础目录

先去Apache官网下载zookeeper-3.7.1.tar.gz,下载完成记得核对sha512校验值,下载包被篡改的情况虽然少见,但核对一下成本很低。把安装包传到三台机器上,统一解压到/opt/zookeeper。然后创建数据目录和日志目录,这一步不要跳过目录规划,后面myid文件、事务日志和快照都会放进去,目录结构越清晰,出了问题越容易定位。

创建完目录,还需要给目录设置好属主。我一般创建一个专用系统用户,比如useradd -r zookeeper,把/opt/zookeeper和/data/zookeeper的属主改成这个用户。用专用用户跑服务的好处是权限可控,即使某个Web应用被攻破了,拿到低权限shell,也不至于直接碰到ZK的数据目录。这个习惯在Kafka那边同样适用,后面我会再提。

3.2 核心配置文件zoo.cfg逐项说明

ZooKeeper的配置集中在conf/zoo.cfg。从模板复制一份,然后重点说明几个关键参数。tickTime=2000是ZK最基本的时间单位,单位毫秒,心跳等很多超时都基于它。initLimit=10表示Follower启动时能容忍的同步初始连接最大超时次数,10乘以tickTime就是20秒。syncLimit=5是Follower和Leader之间心跳的超时次数,也就是10秒。这两个值在3台机器同机房、网络抖动小的情况下,保持默认就够。

clientPort=2181是客户端连接端口。server.1=zk1:2888:3888这种配置,后面第一个端口2888是Follower和Leader之间同步数据的通道,第二个端口3888是Leader选举时的通信端口,这两个端口不能和clientPort混用。dataDir和dataLogDir指向刚才建好的目录。另外建议加上maxClientCnxns=60限制单台客户端连接数,autopurge.snapRetainCount=3和autopurge.purgeInterval=24让ZK自动清理历史快照,避免数据目录无限膨胀。生产环境里这两个autopurge参数尤其重要,我见过ZK目录被快照撑满的情况,加了这个配置之后就再也没出现。

3.3 用myid标记节点身份

集群里每台ZK都要一个唯一ID,这个ID写在dataDir下的myid文件里。三台机器分别执行echo 1 > /data/zookeeper/data/myid、echo 2和echo 3。关键是myid里的数字必须和zoo.cfg里server.x的数字一一对应,比如zk1这台机器上写1,那么zoo.cfg里server.1=zk1:2888:3888指向的必须是这台机器的IP或主机名。

这里有个很多人忽略的细节:server.x后面的主机名,在三台机器的/etc/hosts里必须都能解析到,否则节点启动后互相找不到。我用的是内网IP,直接在zoo.cfg里写IP,省去DNS配置。小集群里这是最不容易出错的方式,但如果你以后要接Kubernetes这类动态环境,还是得用稳定的服务名,因为Pod重启后IP会变。myid文件不要放在共享存储上,每台机器必须有自己独立的myid。

3.4 启动、验证与托管

启动之前先在命令行前台跑一次:zkServer.sh start-foreground,前台模式能把启动日志直接打出来,有配置错误一眼就能看到。确认没有异常再使用zkServer.sh start正常启动,最后用zkServer.sh status查看角色。3台机器都起来之后,应该有一台显示leader,另外两台显示follower,这说明集群选主完成,状态正常。

为了管理方便,我给ZK配了systemd托管,用systemctl start/status/restart控制。这里注意systemd的ExecStart要写成zkServer.sh start-foreground,因为如果写start,脚本会再fork一个子进程,systemd会追踪不到主进程。下面是一个可用的Service片段:

[Unit] Description=Apache ZooKeeper After=network.target

[Service] User=zookeeper Type=forking ExecStart=/opt/zookeeper/bin/zkServer.sh start ExecStop=/opt/zookeeper/bin/zkServer.sh stop Restart=on-failure PIDFile=/data/zookeeper/data/zookeeper_server.pid

[Install] WantedBy=multi-user.target

第一台ZK起来时,日志里可能会出现“Cannot open channel to X at election address”这样的连接告警,这正常,因为另外两台还没起来。等全部节点启动后,告警会消失,不用紧张。但如果三台都起来后status仍然显示Unknown或报错,就要回头查端口占用和防火墙了。

4. 手把手搭Kafka集群:核心配置是成败关键

4.1 下载解压与第一个配置文件

Kafka的安装包同样从Apache官网下载,下载kafka_2.13-3.3.2.tgz,解压到/opt/kafka。需要说明的是,包名里的2.13是Scala编译版本,3.3.2才是Kafka版本。如果以后配Flink或Spark对接Kafka,注意Scala版本要匹配,但那是客户端的事,服务端不涉及。

解压之后先不要急着启动,把config/server.properties完整看一遍。Kafka的配置项非常多,但集群搭建阶段真正需要改的也就十几个,其他保持默认就好。我先给Kafka创建了一个专用系统用户,用户名就叫kafka,然后把/opt/kafka和后续的数据目录属主都改成它。这一步和ZK那边一样,都是为了权限隔离,避免程序以root权限运行。

4.2 server.properties逐项拆解

我第一次搭的时候以为默认配置就能跑,结果启动后所有Broker都注册到了同一个ZK根路径下,行为不可控。正确做法是尽早给集群配置一个独立的ZK chroot,比如/kafka-cluster。在zookeeper.connect这一项写成zk1:2181,zk2:2181,zk3:2181/kafka-cluster,因为ZK本身就是个目录树,加chroot能让Kafka的元数据都挂在/kafka-cluster下面,方便和其他使用ZK的系统隔离。

其他关键参数按实际场景逐项说明:

  • broker.id:每台Broker唯一,建议1、2、3;
  • listeners:监听地址,格式PLAINTEXT://内网IP:9092;
  • advertised.listeners:客户端连接的广播地址,必须填客户端能访问到的IP和端口,这个配置最容易踩坑,很多集群外连接失败都是这个值写成了localhost;
  • log.dirs:数据目录,支持多个路径逗号分隔;
  • num.partitions=3和default.replication.factor=3:新建Topic的默认分区数和副本数,三节点集群就配3副本;
  • min.insync.replicas=2:至少两个副本同步才算写入成功,配合acks=all可以避免数据丢失;
  • offsets.topic.replication.factor=3和transaction.state.log.replication.factor=3:Kafka内部topic的副本因子,也要改成3,否则默认只有1副本,等于把元数据放在单点上。

log.retention.hours=168是数据保留七天,具体保留多久按业务场景来定,不能一概而论。auto.create.topics.enable=false建议直接关掉,防止误操作导致Topic副本数不对,生产环境尤其重要。如果你不确定某个参数的含义,宁可保持默认也不要乱改,Kafka的参数之间经常有联动关系。

4.3 启动Broker并注册到ZooKeeper

配置检查完之后,先在每台机器上用kafka-server-start.sh -daemon /opt/kafka/config/server.properties启动。启动后第一件事是看日志,我在/opt/kafka/logs/server.log里扫一眼有没有ERROR或FATAL,同时用jps确认进程存在,能看到Kafka进程就没大问题。

接着用ZooKeeper客户端验证Broker注册情况。在任意一台ZK上执行zkCli.sh -server zk1:2181,然后执行ls /kafka-cluster/brokers/ids,如果看到[1,2,3],说明三台Broker已经成功注册,这一步标志着集群的底层通道打通了。如果只看到部分ID,就要去对应Broker的server.log里查它为什么没有注册上来,常见原因还是网络问题或者broker.id冲突。

4.4 用Topic验证集群是否真正可用

Broker注册成功不代表消息就能正常流动,必须创建一个Topic并走一遍生产消费验证。创建命令里指定分区数3、副本数3,然后用kafka-topics.sh --describe查看它落在哪些Broker上,正常情况下三个分区Leader会分别落在三台机器上,这是副本均匀分布的表现。如果所有Leader都挤在同一台机器上,说明默认分区分配策略对你当前节点布局不友好,但小集群里一般不会出现。

验证生产和消费,我开两个终端,一个跑kafka-console-producer.sh --bootstrap-server 192.168.1.11:9092 --topic test,另一个跑kafka-console-consumer.sh --bootstrap-server 192.168.1.11:9092 --topic test --from-beginning。生产者这边输入字符串,消费者那边立刻能看到,说明从生产到Broker到消费的整条链路通了。在这个验证过程中,我强烈建议加上--from-beginning,否则新消费组默认只拉取启动之后的新消息,你输入的消息看不到,容易误判成故障。

5. 集群上线后的必备验证与运维动作

5.1 故障转移演练:干掉一个Broker看看会发生什么

集群不是搭起来能跑就算完,你必须亲手验证它到底扛不扛得住故障。最简单有效的演练是:记录当前某个Topic的分区Leader分布,然后找到Leader所在的那台Broker,kill掉Kafka进程,观察集群如何反应。

正常情况下,几秒钟后ZooKeeper会感知到Broker会话超时,Controller会重新为受影响的分区选出新的Leader,Topic仍然可读可写。用kafka-topics.sh --describe再看一遍,Leader已经切换了,ISR列表里也没有刚才那台机器的ID。接下来把杀掉的那个Broker重新启动,它会作为Follower加入集群,慢慢追赶副本数据,ISR恢复成3个。这个过程让我对“Kafka高可用”有了直观的理解:高可用不是靠单机不挂,而是靠副本机制在故障发生时自动顶上。

5.2 可视化工具接入:让集群状态一眼可见

命令行工具能干活,但日常巡检还是得有个可视化界面。Kafka生态里常用有Kafka UI、CMAK和Kafka Eagle。我用的是Kafka UI,它对消费组管理和Topic查看支持得比较好,Docker部署只需要环境变量配置好KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS,指向任意一个Broker地址即可。

可视化工具要提醒一点:它本质上也是一个客户端,会把集群的元数据拉一遍,在生产环境接入时要控制刷新频率,不要开好几套工具轮询同一个集群,否则会白白增加Broker的负担。管理类操作,比如变更分区、删除Topic,我建议仍然在命令行里做,界面只用于观察。可视化工具对新手最大的价值是能直观展示分区副本分布和消费组Lag,这两项数据比看一堆日志更容易建立对集群的体感。

5.3 消息延迟高与消费堆积的排查套路

上线之后最常被找上门的问题就是“消息延迟高”。我的排查顺序固定是:先看消费组Lag,再看生产端,最后看Broker性能,不要一上来就重启Broker。查消费Lag用kafka-consumer-groups.sh --bootstrap-server 192.168.1.11:9092 --describe --group 你的消费组,如果某个分区LAG一直增长,那问题大概率在消费端,先看消费逻辑和数据库连接池有没有瓶颈。

如果Lag为0但端到端延迟还是高,那重点转向生产端:确认生产者的acks配置,acks=all会比acks=1慢不少,但数据安全性更高;同时确认Broker的磁盘IO和CPU是不是已经到了瓶颈,用iostat看磁盘util,如果长时间超过85%,说明磁盘拖后腿了,扩容或换SSD是硬道理。延迟问题很少是单点原因,把链路拆开一段一段量,才能定位准确。

5.4 扩容与升级的注意事项

集群上线半年后业务变大,扩容是迟早的事。Kafka加Broker相对简单:新机器装好Kafka,改一个不同的broker.id,zookeeper.connect指向同一个chroot,启动后它就会加入集群。但注意,新Broker加入后,已经存在的分区不会自动往它上面迁移,需要手动把部分分区重分配过去,否则新节点就是空转。

ZooKeeper集群扩容更敏感,不要一下子加两台,容易引发重新选举。我建议扩容时一次加一台,确认follower角色正常,再继续下一台。升级方面,Kafka和ZK的版本升级都遵循同一个原则:先升级ZooKeeper,再滚动升级Kafka Broker。每一台Broker的升级都是杀掉再启动,消费者端要配合重试机制,否则滚动窗口期的报错会把你手机打爆。升级前务必把server.properties完整备份,升级后逐一核对配置差异,这是最简单也最容易被跳过的步骤。

6. 常见问题排查实录:这些坑我替你踩过了

6.1 Broker启动后老是连接不上ZooKeeper

这个问题我遇到过不止一次,原因五花八门。最基础的是网络不通,先telnet zk1 2181看端口是否能通,不通就查防火墙和安全组。然后确认zookeeper.connect地址写的是否和ZK实际监听地址一致,这里最好直接写内网IP而不是主机名,省去DNS解析的变数。最后还要看chroot路径,如果用/kafka-cluster,先确认ZK里已经存在这个znode,否则Kafka启动时会尝试创建但可能因为权限问题失败。日志里如果滚动刷“Unable to connect to zookeeper”,把这几个方向按顺序查一遍,基本都能解决。

6.2 Topic创建成功但生产消费都报错

Topic能创建,说明Broker和ZK正常,但生产消费报错时,优先级最高的是检查advertised.listeners。排查时我见过太多案例:本地测试改成localhost能跑,到了集群外客户端就超时,报错提示metadata没有返回。这是因为Broker广播给客户端的连接地址就是advertised.listeners里的值,客户端拿到这个地址去连,如果连的是localhost或者内网不可达的地址,自然失败。

解决方法是把advertised.listeners显式写成客户端可达的IP:端口,比如PLAINTEXT://192.168.1.11:9092,然后重启Broker。这个配置不重启不会生效,改完一定要挨个重启。如果你发现改了之后客户端还是连不上,还有一种可能是Broker的listeners同时监听了多个网卡,这时候要用listeners和advertised.listeners成对配置,保证广播出去的地址确实是被监听的地址。

6.3 节点重启后一直处于UnderReplicated状态

Broker故障恢复后,有时候kafka-topics.sh --describe看到ISR一直不是满的,比如3副本分区只有2个副本在ISR里。这多半是新加入的副本追数据太慢,数据量很大时,同步需要时间,正常现象,等一会儿再看即可。但如果是长时间卡住,就要看这台Broker的磁盘空间和复制线程配置,磁盘满了或者replica.fetch.max.bytes太小都会让追齐变得困难。

还有一种隐蔽情况:Broker已经重新注册到ZK,但机器时间不统一,导致offset校验错乱。这时候回到第2节说的时间同步问题,把ntp/chrony跑好,再触发一次分区重分配,基本就能恢复。排查UnderReplicated时不要只看一个Topic,要整体看所有Topic的ISR状态,如果所有分区都缺同一台Broker,那是节点问题;如果只有个别分区缺,那更可能是那个分区

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询