在消息中间件的选型落地中,Kafka 几乎已经成了事实标准。我这两年经手的实时数据链路,凡是吞吐量要求高一点的场景,基本都绕不开它。CentOS 7 虽然已经不是最新的系统,但它在很多存量服务器和云主机里依然占着相当大的比例,很多公司线上跑着的就是这一套。把 Kafka 消息集群跑在 CentOS 7 上,用得稳不稳,关键看你前期规划做没做到位。本篇就是我基于这套组合的一次完整复盘,从环境识别到节点分工,再到 ZooKeeper 与 Broker 的逐项配置、验证和踩坑排查,希望给正打算动手搭集群的读者一条能直接参考的路线。
先说清楚这篇文章适合谁。如果你只是在单机上拿 Docker 起一个 Kafka 练手,那这篇可能偏重了;如果你是要在三台 CentOS 7 物理机或者云主机上搭一个生产可用的多 Broker 集群,面对server.properties里一堆参数不知道该动哪些、动了之后会遇到什么问题,那这篇文章正好对症。我假设你手头没有现成的 Puppeteer 一类的自动化管理工具,就是纯手工下载安装包、改配置、起服务,这也是多数中小团队最常用的方式。
1. 集群规划先行:版本、拓扑与系统预检
1.1 为什么先别急着下载安装包
很多人装 Kafka 集群,上来就是wget下载压缩包,然后解压、改配置、启动,等发现各种连接不上再回头翻文档,时间全耗在来回试错上了。我自己的习惯是反过来的:先花半天把版本匹配关系、节点分工和系统参数理顺,后面真正动手的时间往往不到半小时。
这里有个容易被忽略的核心问题:Kafka 从很早期就一直依赖 ZooKeeper 做协调服务,集群的 broker 元数据、Topic 分区状态、消费者组的 offset 都记录在 ZooKeeper 里。所以你要搭的是两个集群:一个是 ZooKeeper 集群,一个是 Kafka Broker 集群。在 CentOS 7 上这两个服务通常都是独立部署,ZooKeeper 一般用三个节点组成奇数投票集群,Kafka Broker 节点数量则根据吞吐需求来定,常见是三台起步。
1.2 版本组合:CentOS 7 的兼容性细节
CentOS 7 的内核版本是 3.10.x,这个版本不新,但足够稳定。Kafka 官方对操作系统的要求并不严格,真正制约我们的是 JDK 版本。
我用的是 JDK 1.8。为什么不用 11 或 17?一方面 CentOS 7 自带的 yum 源里 JDK 版本更新不及时,另一方面 Kafka 2.x 系列在 JDK 8 上跑得非常成熟,生产环境用起来最省心。如果你要玩 Kafka 3.x,JDK 8 也依然支持,但如果你是纯小白,我更建议先用 Kafka 2.8.x 以下的版本配合 JDK 8 把集群跑通,等熟悉了再升。
版本选型的另一个重要维度是 Kafka 与 ZooKeeper 的配套。以 Kafka 2.6.0 为例,它推荐的 ZooKeeper 版本是 3.6.x 或 3.5.x。我实际用的是 ZooKeeper 3.6.3,整体很稳定。不要直接拿 ZooKeeper 最新的大版本配老 Kafka,版本之间协议不兼容的问题一旦出现,日志里报错会让人摸不着头脑。
1.3 节点规划与系统预检命令
做规划的时候,要提前把每一台机器的角色、IP、数据盘挂载点、内存大小写清楚。下面的表格是我常用的规划模板:
| 主机 | 角色 | 内网IP示例 | 数据目录 | 规格 |
|---|---|---|---|---|
| kafka-zk-1 | ZooKeeper + Broker | 192.168.1.101 | /data/zk、/data/kafka | 4核 8G |
| kafka-zk-2 | ZooKeeper + Broker | 192.168.1.102 | /data/zk、/data/kafka | 4核 8G |
| kafka-zk-3 | ZooKeeper + Broker | 192.168.1.103 | /data/zk、/data/kafka | 4核 8G |
注意这里我用的是“ZooKeeper + Broker”组合部署。小规模集群完全可以这么干,省机器,也方便维护。如果你的业务量很大,ZooKeeper 和 Kafka 最好分开成独立节点,不过那种规模的集群已经不适用本篇思路了。
系统预检的核心命令有这么几条:
cat /etc/redhat-release确认系统版本free -h看内存df -h确认数据盘hostname -I检查内网 IPsystemctl status firewalld看看防火墙状态
另外要提一句:生产环境里尽量把数据目录放在独立挂载的数据盘上,不要放在/根目录分区。Kafka 和 ZooKeeper 对磁盘 IO 都比较敏感,系统盘如果还要跑日志、跑系统服务,很容易互相干扰。实测中,数据盘和系统盘分离,同等条件下集群性能要稳不少。
2. 两个基础依赖:JDK 安装与 ZooKeeper 集群搭建
2.1 JDK 1.8 的安装与校验
CentOS 7 上装 JDK 有很多种方式,最省事的是用yum安装 OpenJDK。我用的是java-1.8.0-openjdk-devel这个包,注意要装devel版本,里面包含完整的 JRE 和常用开发工具。如果只装java-1.8.0-openjdk,后面有些命令会缺失。
yum install -y java-1.8.0-openjdk-devel java -version javac -version这两条命令都要能正常输出版本信息。在实际操作中我发现,有些云镜像的 CentOS 7 自带了一个很老的 JDK,比如 1.7,这时候要先检查alternatives --config java,把默认 Java 版本切到 1.8。
环境变量方面,OpenJDK 的安装路径一般是/usr/lib/jvm/java-1.8.0-openjdk...,如果不做特殊处理,系统也能自动找到。但我在后面配置免密执行脚本的时候,还是建议把JAVA_HOME写进/etc/profile,避免某些启动脚本找不到 Java:
cat >> /etc/profile <<EOF export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk export PATH=\$PATH:\$JAVA_HOME/bin EOF source /etc/profile2.2 下载与解压 ZooKeeper
ZooKeeper 官方二进制包一般叫apache-zookeeper-3.6.3-bin.tar.gz。这里有个容易犯的错:下载了不带-bin的源码包,结果解压后没有可以直接运行的 jar,然后各种报错。认准-bin版本。
wget https://mirrors.xxx.com/apache/zookeeper/zookeeper-3.6.3/apache-zookeeper-3.6.3-bin.tar.gz tar -zxvf apache-zookeeper-3.6.3-bin.tar.gz -C /opt/ ln -s /opt/apache-zookeeper-3.6.3-bin /opt/zookeeper mkdir -p /data/zk/{data,logs}/data/zk/data用于存放 ZooKeeper 的数据快照,/data/zk/logs用于存放事务日志。生产环境里这两类文件也建议分目录存放,方便排查问题。
2.3 三台机器上的 zoo.cfg 配置细节
ZooKeeper 集群的配置集中在conf/zoo.cfg。默认情况下 ZooKeeper 只提供了一个zoo_sample.cfg,你需要把它复制成zoo.cfg。
tickTime=2000 initLimit=10 syncLimit=5 dataDir=/data/zk/data dataLogDir=/data/zk/logs clientPort=2181 server.1=192.168.1.101:2888:3888 server.2=192.168.1.102:2888:3888 server.3=192.168.1.103:2888:3888解释一下几个关键参数:
tickTime=2000:ZooKeeper 内部的基础时间单位,单位毫秒,也就是 2 秒。initLimit=10:节点间建立连接后,初始化同步最多允许的 tick 数,这里是 20 秒。syncLimit=5:处理中的 leader 与 follower 之间同步的最大 tick 数,超过这个数 follower 会被视为失联。server.1=IP:2888:3888:2888 用于 leader 与 follower 之间的通信,3888 用于选举投票。
三台机器的zoo.cfg基本一样,唯一不同的是dataDir目录下的myid文件内容。myid文件里就写一个数字,对应当前机器在server.X里的编号。比如第一台机器:
echo "1" > /data/zk/data/myid chown -R kafka:kafka /data/zk如果你不创建用户直接 root 运行也问题不大,但既然学了集群,还是规范一点,后面我会专门说一下服务账号的问题。
2.4 启动 ZooKeeper 集群并检查状态
三台机器分别执行:
/opt/zookeeper/bin/zkServer.sh start然后看状态:
/opt/zookeeper/bin/zkServer.sh status正常情况下,三台机器中会有一台显示Mode: leader,另外两台显示Mode: follower。如果全部显示 standalone 或者一直报连接超时,先别急着看 Kafka,优先排查myid是否写对、syncLimit是否调得太小、防火墙 2888/3888 端口是否放通。
这里想特别强调一下,ZooKeeper 的2181是给客户端用的,你不需要在防火墙上对公网放行,内网放行即可。
3. Broker 核心配置拆解:server.properties 里的关键取舍
3.1 Kafka 下载与目录规划
ZooKeeper 集群稳定运行之后,才能开始搭 Kafka Broker 层。Kafka 二进制包同样在国内镜像源或 Apache 官网下载。我用的是kafka_2.12-2.6.0.tgz,其中2.12是打包时使用的 Scala 版本,2.6.0是 Kafka 版本。对于初学者,建议使用跟官方文档一致的版本号,以免网上教程对不上。
tar -zxvf kafka_2.12-2.6.0.tgz -C /opt/ ln -s /opt/kafka_2.12-2.6.0 /opt/kafka mkdir -p /data/kafka/logs每个 Broker 的数据目录是/data/kafka/logs,在server.properties里用log.dirs指定。注意它叫log.dirs,很多人会误以为这是 Kafka 的系统日志目录,其实它是存放分区数据的地方。Kafka 的系统运行日志由启动脚本里的log4j配置控制,别搞混。
3.2 核心参数逐个说明
config/server.properties是 Kafka 集群最核心的配置文件,我把每次装集群都要修改的参数列出来并说明理由:
| 参数 | 值示例 | 作用与理由 |
|---|---|---|
| broker.id | 0 / 1 / 2 | 集群内唯一标识,三台分别设为 0、1、2 |
| listeners | PLAINTEXT://192.168.1.101:9092 | 客户端连接的监听地址,填实际内网 IP,不要填localhost |
| advertised.listeners | PLAINTEXT://192.168.1.101:9092 | 注册到 ZooKeeper 并通告给客户端的地址,这是最常见的连接拒绝原因 |
| zookeeper.connect | 192.168.1.101:2181,192.168.1.102:2181,192.168.1.103:2181 | 指定 ZooKeeper 集群所有节点 |
| log.dirs | /data/kafka/logs | 分区数据目录 |
| num.partitions | 3 | 默认分区数,结合业务扩展需求设 |
| default.replication.factor | 2 | 默认副本数,生产环境建议至少 2,3 副本更好 |
| min.insync.replicas | 1 | 控制写入成功所需的最小同步副本数,生产环境可设 2 |
| log.retention.hours | 168 | 数据保留 7 天,按业务需求调 |
| auto.create.topics.enable | true | 建议线上变成 false,避免误创建 Topic |
3.3 advertised.listeners 的重要性
advertised.listeners可能是整个 Kafka 集群配置里最坑的一项。它决定了客户端拿到 Broker 地址后去连接哪个 IP 和端口。如果你不配置它,Kafka 会默认把listeners里绑定的地址发布出去。但很多时候listeners绑定的是内网 IP,如果客户端从外部网络访问,或者你的环境里有 NAT 转发,就必须显式写advertised.listeners。
我在测试环境就踩过这样的坑:Broker 配置绑定了localhost,然后生产端从另一台机器怎么都连接不成功,报错信息是Connection refused或者timed out。排查半天,最后发现就是 advertised 地址写成了localhost,客户端拿着localhost去连接当然连不上。
正确的做法是:listeners绑定到当前机器的实际 IP 或0.0.0.0,advertised.listeners也写实际 IP。如果你有负载均衡或公网接入,再换成对应的外网地址。总之,客户端能访问到的地址,才是你要填进advertised.listeners的地址。
4. 三台 Broker 的启动与集群状态验证
4.1 分发配置的常见方式
很多新手会在三台机器上分别解压一份 Kafka,然后手动改server.properties。这样比较慢,也容易改错。更高效的做法是:第一台机器解压并改好,然后通过scp把整个目录分发到另外两台,再单独修改三台机器不同的broker.id和listeners、advertised.listeners。
scp -r /opt/kafka root@192.168.1.102:/opt/ scp -r /opt/kafka root@192.168.1.103:/opt/分发之后,第二台机器改三处:broker.id=1、listeners=PLAINTEXT://192.168.1.102:9092、advertised.listeners=PLAINTEXT://192.168.1.102:9092。第三台同理。
这里顺便提一句,如果你的 Kafka 目录是放在像/opt这样的系统目录,还是建议创建一个专用的启动用户。比如:
useradd kafka chown -R kafka:kafka /opt/kafka /data/kafka用专用用户跑 Kafka 服务,可以避免因为误操作 root 权限导致的问题,也更符合生产环境的账号治理要求。
4.2 启动 Broker 并核对日志
启动 Kafka Broker 的时候,不要在根目录下直接执行bin/kafka-server-start.sh,我建议这样写:
nohup /opt/kafka/bin/kafka-server-start.sh /opt/kafka/config/server.properties > /dev/null 2>&1 &然后在/data/kafka/logs里你可能发现没产生任何日志,那是因为 Kafka 的系统日志默认输出到启动脚本所在路径下的logs目录,或者由log4j.properties指定。不同版本略有差异,总之找不到日志时先找一下log4j.properties里的配置。
启动成功后,最直接的验证方式就是看日志。日志里会出现一行类似于:
[KafkaServer id=0] Started (kafka.server.KafkaServer)这说明 Broker 已经成功注册到 ZooKeeper 并加入 Kafka 集群了。
4.3 用命令行工具验证集群状态
三台 Broker 全部启动后,在任意一台机器上执行:
/opt/kafka/bin/kafka-broker-api-versions.sh --bootstrap-server 192.168.1.101:9092正常会输出一长串 API 版本支持情况,同时能看到 broker 列表。还有一条更直观的命令是查看 Topic 列表:
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 192.168.1.101:9092 --list如果你在创建 Topic 之前执行,返回结果应该是空的,但命令本身不会报错。这就能证明客户端和 Broker 之间的连接是通的。如果这一步报Connection refused,优先查 advertised.listeners 和防火墙。
5. 从 Topic 创建到生产消费验证:确认链路真正打通
5.1 创建一个带副本的 Topic
在没有验证之前,不要着急往里灌数据。先手动创建一个带副本的 Topic:
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 192.168.1.101:9092 \ --create --topic test-topic \ --partitions 3 --replication-factor 2--replication-factor 2的意思是每个分区保存两个副本,分布在不同的 Broker 上。创建完成后查看详情:
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 192.168.1.101:9092 \ --describe --topic test-topic你会看到分区信息,类似:
Topic: test-topic Partition: 0 Leader: 0 Replicas: [0,1] Isr: [0,1]这里有三个关键信息:Leader 是当前负责该分区读写的主副本,Replicas 是分配的副本列表,Isr 是当前处于同步状态的副本列表。如果有副本并没有追上 Leader,Isr 里就不会出现它的编号。所谓高可用,本质上就是围绕 Leader 和 Isr 的切换。
5.2 用控制台工具做生产消费测试
验证生产和消费,最简单的方式是 Kafka 自带的 console 工具。开一个终端执行生产:
/opt/kafka/bin/kafka-console-producer.sh --bootstrap-server 192.168.1.101:9092 \ --topic test-topic输入几行测试数据,比如hello、world-test-1等。再开一个终端执行消费:
/opt/kafka/bin/kafka-console-consumer.sh --bootstrap-server 192.168.1.101:9092 \ --topic test-topic --from-beginning如果能看到你之前输入的数据,说明生产、消费、ZooKeeper 协调这一整条链路已经通了。这里的--from-beginning表示从最早的 offset 开始读取,便于验证。
5.3 模拟故障:验证集群的自愈能力
集群搭完,不验证一下故障转移能力就等于白搭。最简单的演练方式是直接停掉一台 broker。比如我们停掉 broker.id=0 对应的那台机器:
kill <kafkaServerPID>然后再次执行:
/opt/kafka/bin/kafka-topics.sh --bootstrap-server 192.168.1.101:9092 \ --describe --topic test-topic你会发现原先 Leader 是 0 的分区,Leader 自动切换到了 1 或者 2,Isr 列表里也把 0 剔除了。整个切换过程通常只需要几秒到十几秒。这就证明集群确实具备高可用能力,而不是只有一个进程在运行。
这里还要提醒一句,如果 Topic 的副本因子是 1,Broker 宕机时这个分区就完全不可用了。所以在生产环境里,副本因子设成 2 以上是硬要求,这也是早期最容易被忽略的配置项。
6. ZooKeeper 与 Broker 的日常运维细节
6.1 服务账号、日志轮转与开机自启
走到这一步,很多人的集群已经能跑了,但离“生产可用”还差最后几趴。首先是服务账号,前面提到过,建议建一个专门的kafka用户。其次,Kafka 和 ZooKeeper 默认是前台脚本启动方式,服务器重启后不会自动拉起生产环境。要搞一个 systemd 服务单元。举一个 Kafka 的例子:
[Unit] Description=Kafka Broker After=network.target [Service] Type=simple User=kafka Group=kafka ExecStart=/opt/kafka/bin/kafka-server-start.sh /opt/kafka/config/server.properties ExecStop=/opt/kafka/bin/kafka-server-stop.sh Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target把这个文件放到/etc/systemd/system/kafka.service,然后执行:
systemctl daemon-reload systemctl enable kafka systemctl start kafkaZooKeeper 同理,它官方也提供了zkServer.sh之外适合 systemd 的启动方式,直接用脚本路径作为 ExecStart 就行。开机自启这个问题,如果不提前处理,一旦机器因维护重启,集群只剩一半节点,手动拉起很容易遗漏。
6.2 几个容易被日志淹没的问题排查思路
日常运维里,你可能会遇到 Kafka 日志疯狂刷Disconnected from zookeeper,这时候不要慌,先看几样东西:
- ZooKeeper 的三台是不是都还活着,
zkServer.sh status看一遍 - Kafka 进程是否还在,
ps -ef | grep kafka - 网络层有没有丢包或延迟,
ping一下对端
如果 ZooKeeper 活着、网络也通,再看 Kafka 的log4j日志文件,里面通常有具体的异常栈。有些问题看起来是 ZooKeeper 断连,实际上是 Kafka Broker 的垃圾回收耗尽了 CPU 或 Java 堆内存,导致心跳超时被 ZooKeeper 踢掉。这种时候优先调 JVM 堆,不要只盯着 ZooKeeper 配置改。
还有一个常见问题是磁盘空间。Kafka 的默认保留策略会根据log.retention.hours删除旧数据,但如果你的分区数量多、消息量大,磁盘还是会被写满。数据目录写满后,Broker 会直接下线对应分区,日志里出现There are no more offsets available之类的报错。运维上建议对/data/kafka做独立的监控和告警,磁盘使用率超过 80% 就该注意了。
6.3 常用管理命令清单
我总结了一份日常最常用的命令清单,贴在这里备用:
| 操作 | 命令 |
|---|---|
| 查看 Topic 列表 | kafka-topics.sh --bootstrap-server $BROKER --list |
| 查看 Topic 详情 | kafka-topics.sh --bootstrap-server $BROKER --describe --topic $TOPIC |
| 查看消费者组 | kafka-consumer-groups.sh --bootstrap-server $BROKER --list |
| 查看消费者组滞后 | kafka-consumer-groups.sh --bootstrap-server $BROKER --describe --group $GROUP |
| 修改 Topic 分区数 | kafka-topics.sh --bootstrap-server $BROKER --alter --topic $TOPIC --partitions N |
| 删除 Topic | kafka-topics.sh --bootstrap-server $BROKER --delete --topic $TOPIC |
消费者组 lag(滞后量)是日常监控重点关注的数据。如果 lag 一直增加,说明消费能力跟不上了,要么加消费者,要么扩展分区数。
7. 复盘:那些我踩过并希望你避免的坑
7.1 第一个坑:listen 地址写错导致跨机消费失败
这个在前面已经详细讲过了,advertised.listeners没配置或者填了localhost是最典型的问题。我再补充一个容易混淆的点:很多教程里会写listeners=PLAINTEXT://192.168.1.101:9092,但也有人尝试用PLAINTEXT://0.0.0.0:9092,然后advertised.listeners写实际 IP。这两种写法都能用,关键是不要让两个参数的语义打架。listeners决定进程绑定哪个网卡端口,advertised.listeners决定客户端连接哪个地址,从客户端到 broker 之间所有网络转发层面的地址变换,都必须反映在advertised.listeners里。
7.2 第二个坑:ZooKeeper 三节点中有一个没起来
如果三台 ZooKeeper 里有一台没起来,整个 Kafka 集群可能还能撑一会儿,但状态是不健康的。你可能会看到某些请求超时,或者 Broker 之间出现脑裂现象前的各种怪日志。排查时先检查 myid 文件,因为server.X编号必须和myid完全对齐。比如server.2对应的机器,myid必须是 2,不是 3。
还有一个小细节:dataDir不能有残留的旧快照,如果你之前用单机模式跑过 ZooKeeper,再切换成集群模式,旧的数据可能干扰选举。最干净的办法是把旧环境的数据目录清空再重新初始化集群。
7.3 第三个坑:安全组和防火墙只放行了 9092
一般人在云控制台或防火墙里只放行 9092,觉得客户端只要能连上 9092 就能正常收发消息。这句话对 Broker 服务是成立的,但它忽略了集群内部通信的端口。ZooKeeper 之间的 2888、3888,Broker 与 Broker 之间的 9092 都属于内网互通范围。如果你的内网安全组没有放行全部内网 IP 的互访,建议统一加上规则,或者至少保证这些端口在集群各节点之间可通。
7.4 第四个坑:默认 JVM 内存太小
Kafka 的kafka-server-start.sh脚本里默认设置了KAFKA_HEAP_OPTS,通常默认只有 1G。如果你的机器是 8G 内存,加上 ZooKeeper 和系统其他进程,默认堆偏小会导致频繁 GC,进而表现为 Broker 偶尔卡顿、消费者延迟增大。建议根据机器内存调整,比如 8G 内存的机器可以把 Kafka 堆设置到 4G:
export KAFKA_HEAP_OPTS="-Xmx4g -Xms4g"在kafka-server-start.sh里增加或者修改这行即可。-Xms和-Xmx设置成相同值,可以减少运行期间堆动态扩容带来的性能抖动。
7.5 第五个坑:依赖环境的时区和时钟同步
Kafka 集群对时间同步其实很敏感。如果三台机器的系统时间差得太多,ZooKeeper 和 Kafka 之间的会话过期、事务顺序异常各种问题都会冒出来。CentOS 7 上通常用chrony或ntpdate做时间同步。装完系统后第一时间配置好时钟同步源,可以省掉很多莫名其妙的故障排查时间。
我个人的习惯是,装完 CentOS 7 之后先做四件事:更新 yum 源、装 JDK、配时钟同步、建专用账号。做完这四件事,再开始部署 ZooKeeper 和 Kafka,后面基本不会出现因环境基础问题引入的幺蛾子。
关于后续扩展,我还想多说一句:如果这套集群后面要接入 Kafka 的跨集群同步工具,比如 MirrorMaker 之类的,那advertised.listeners和网络规划这一步更要提前设计好,因为跨集群数据同步会反复引入新的网络地址问题,前期留好统一的前缀和端口规划,以后会省很多事。