1. 站在2.8这个版本门槛前,为什么要去掉Zookeeper
先说结论:Kafka从2.8版本开始,正式允许你在一片没有Zookeeper的世界里运行Kafka集群。这可不是一个小改动,这是Kafka社区憋了好几年的大招——把Zookeeper从Kafka的架构里踢出去,让Kafka自己管理自己的元数据。官方管这套新机制叫KRaft模式,对应的就是传统的ZooKeeper模式。
我在第一次听到这个消息的时候,第一反应是"终于来了"。做Kafka运维的人都清楚,Zookeeper一直是Kafka集群里最让人头疼的部分。它本身是一个独立的分布式协调系统,有自己的一套选举机制、会话超时、数据模型,你得额外维护一整套ZK集群,还得操心它的性能、监控、扩容。尤其是当Kafka的 broker 节点数量变大、分区数量变多之后,Zookeeper常常成为瓶颈。很多生产事故的根源,最后都能追溯到Zookeeper的GC停顿、session超时、节点脑裂之类的问题上。
Kafka 2.8里KRaft模式还标注为"生产环境初步可用",也就是说官方认为它已经能跑生产负载了,但仍未完全替代ZooKeeper模式。真正到了3.3之后,KRaft才被标记为生产就绪,而ZooKeeper模式在3.5版本被标记为deprecated。但2.8这个版本对于想提前尝鲜、想了解这套新架构原理的人来说,反而是个特别好的切入窗口。
这篇文章主要面向两类人。一类是原来维护过ZooKeeper模式Kafka集群的运维或者开发,想搞清楚KRaft到底改了什么、部署配置和以前有什么不同;另一类是从零开始接触Kafka、没怎么碰过ZK的新手,想直接按照KRaft模式把一套集群搭起来。写的时候我会把配置文件的每个参数都拆开讲,尽量让你照着操作就能复现。我自己的建议是:如果你要在生产环境上跑,可以考虑直接上3.3以上版本;但如果你想理解这套机制的设计思路,2.8的实验集群是非常好的学习载体。
2. KRaft模式下,Kafka内部到底发生了什么变化
在动手部署之前,先把架构层面的东西聊透。很多人一上来就照着配置文件敲,敲完发现集群起不来,然后各种排查,最后发现是对新架构理解有偏差。与其这样,不如先花十分钟弄清楚KRaft模式下Kafka的组件边界、角色划分和数据流,后面排查问题会轻松很多。
2.1 控制平面和数据平面:从"外部协调"到"内部自治"
在ZooKeeper架构下,Kafka的元数据存放在外部的ZK集群里,包括:
- 集群里有哪些broker节点
- 每个Topic有哪些分区、分区副本分布在哪些broker上
- 每个分区的ISR(同步副本)列表
- 消费者组的位移信息(旧版本依赖ZK,新版本用内部Topic存储)
- 各种动态配置
Kafka的控制器(Controller)是一个特殊的broker角色,它负责监听ZK中的状态变化,然后向其他broker下发指令。一旦控制器所在的broker挂了,剩下的broker要通过ZK重新选举一个新的控制器。这里涉及到大量的分布式协调逻辑,ZK的读写在高峰期会成为瓶颈。
KRaft模式做的事情,本质上是把ZK负责的那部分元数据存储功能,搬进了Kafka自己的内部日志系统。这个内部日志系统本身就是一个单分区的Topic,名字叫__cluster_metadata。Kafka用自己最擅长的"复制日志"机制来保证元数据的一致性和高可用,不再需要一个外部组件来协调。
这样改完之后,控制平面(元数据管理)和数据平面(消息读写)就完全统一在Kafka自身内部了。你不需要再关心ZK集群的容量规划,不需要再单独监控ZK的健康状态,也不需要担心ZK和Kafka的版本兼容性。整个系统的组件数量减少了一个,这在运维层面是一个巨大的简化。
2.2 Controller节点与Broker节点:两种角色的分离
在KRaft模式下,节点角色被抽象得更加清晰。一个节点可以是:
- Controller(控制器节点):只负责元数据管理,不处理数据读写
- Broker(数据节点):只处理消息的读写请求,不参与元数据管理
- Combined(混合节点):同时承担Controller和Broker两种角色
在2.8版本里,官方推荐的做法是用三个节点组成一个Controller集群,再用若干个节点组成Broker集群。但小规模实验环境完全可以用三个Combined节点,也就是每个节点既当Controller又当Broker,这样最省机器。后面我会给出这种部署的完整配置。
这里有个很重要的变化:在ZK模式下,Controller是从所有broker中选举出来的一个,谁当选谁就是Controller;而KRaft模式下,Controller是一个独立配置的角色,一个节点是不是Controller,写在配置文件里,启动时固定下来。换句话说,Controller不再是"选"出来的,而是"配置"出来的。
不过需要说明的是,Controller节点之间仍然有选举机制。多个Controller节点会通过KRaft内部的投票协议选出一个Active Controller,其余作为Standby Controller。只是这个过程不依赖ZK了,而是基于元数据日志的复制和投票完成的。
2.3 元数据日志:__cluster_metadata内部Topic的工作方式
KRaft模式的核心数据结构是元数据日志,它记录着集群所有元数据的变更事件,每条记录就是一次元数据变更。比如"创建了某个Topic"、"某个分区Leader切换了"、"某个broker上线了",这些事件都会被追加到元数据日志中。
这个日志被保存在所有Controller节点上,并且是持久化的。Controller节点通过类似Raft共识算法的方式,在日志条目之间进行复制和投票,保证所有存活的Controller节点对元数据日志的内容达成一致。当一个Controller节点从故障中恢复时,它会重新读取元数据日志,把自己的状态恢复到最新。
在2.8版本里,这个元数据日志还只能在Controller节点上读写。Broker节点会定期从Active Controller拉取元数据快照和变更事件,然后缓存在本地。所以Broker本地的元数据可能比Controller稍微滞后一点,但不会差很多,因为Controller会主动推送变更。
需要特别注意的地方是:__cluster_metadata这个内部Topic不是由你手动创建的,它是Kafka在格式化存储目录的时候自动生成的。你不需要也不应该手动去干预它,否则会破坏元数据的一致性。
3. 环境准备与安装过程:从JDK到Kafka二进制包
这部分实际操作起来并不复杂,但有几个细节容易踩坑,我一个个说清楚。我的实验环境规划是三台Linux服务器,每台2核4G内存,系统是CentOS 7。
3.1 三台机器的节点规划
先规划好角色和IP,后面配置的时候直接对照着写就行:
| 节点 | IP | Controller角色 | Broker角色 | 主机名 |
|---|---|---|---|---|
| node1 | 192.168.10.11 | 是 | 是 | kafka1 |
| node2 | 192.168.10.12 | 是 | 是 | kafka2 |
| node3 | 192.168.10.13 | 是 | 是 | kafka3 |
三个节点都配置为Combined角色,也就是既当Controller又当Broker。这是官方文档里的小规模部署推荐方式,同时也是最省资源的方式。如果你以后要扩展,可以把Controller和Broker拆开部署,但从这个基础架构往任何方向演进都不难。
3.2 JDK环境检查
Kafka 2.8运行需要Java 8或者Java 11,我自己用的是JDK 1.8。先检查一下系统里面有没有装:
java -version如果输出类似下面这样的内容,说明环境OK:
java version "1.8.0_292" Java(TM) SE Runtime Environment (build 1.8.0_292-b10) Java HotSpot(TM) 64-Bit Server VM (build 25.292-b10, mixed mode)如果没装,用包管理器装一个:
sudo yum install -y java-1.8.0-openjdk-devel装完之后确认JAVA_HOME环境变量是不是正确指向了JDK路径。Kafka的启动脚本kafka-server-start.sh会读取这个变量,如果它没设置,脚本会尝试自动查找Java路径,但最好还是手动配置。
3.3 Kafka 2.8安装包下载与部署目录规划
Kafka的二进制包会同时包含ZooKeeper相关组件和KRaft相关组件,你不需要额外下载什么特殊版本。下载地址这里就不贴了,直接去Apache官网下载kafka_2.13-2.8.0.tgz,大概几十MB。
三台机器都执行同样的下载和解压操作:
cd /opt wget https://downloads.apache.org/kafka/2.8.0/kafka_2.13-2.8.0.tgz tar -zxvf kafka_2.13-2.8.0.tgz mv kafka_2.13-2.8.0 kafka注意Kafka安装包文件名里的2.13是Scala编译版本,后面的2.8.0才是Kafka自身版本,两者不要搞混。在写依赖或者做版本兼容性判断的时候,要明确用的是哪一个。
4. 配置文件逐行拆解:server.properties里每个参数的含义
KRaft模式下,Kafka的配置文件还是叫server.properties,但里面的参数和ZK模式有很多不同。很多刚开始接触KRaft的人最不习惯的就是这一点——你打开配置,发现zookeeper.connect不见了,多了几个全新的controller.*参数,然后整个人的思路就乱了。这里我把核心参数一个一个解释清楚。
4.1 基础标识参数
process.roles=broker,controller node.id=1 controller.quorum.voters=1@kafka1:9093,2@kafka2:9093,3@kafka3:9093process.roles:这是KRaft模式新增的核心参数,指定节点承担什么角色。可选值有broker、controller、broker,controller。三个节点这里都填broker,controller,表示同时承担两种角色。node.id:节点的唯一ID,在ZK模式下叫broker.id,KRaft模式把它改名为node.id。三个节点分别是1、2、3,全局唯一。controller.quorum.voters:这个参数是KRaft最关键的配置,它列出了所有Controller节点的地址和端口,格式是nodeId@host:port,nodeId@host:port。注意,这里写的是"Controller之间的通信端口",不是客户端访问Kafka的端口,也不是broker之间通信的端口。所有节点上的这个参数必须完全一致。
这里有一个很容易搞混的点:controller.quorum.voters里的端口9093是用来跑Controller之间元数据复制和投票的,它和broker对外提供消息读写服务的端口9092是不同的。在Combined模式下,一个节点会同时监听两个端口,各干各的。
4.2 网络监听参数
listeners=PLAINTEXT://kafka1:9092,CONTROLLER://kafka1:9093 advertised.listeners=PLAINTEXT://kafka1:9092 controller.listener.names=CONTROLLER listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL inter.broker.listener.name=PLAINTEXTlisteners:节点启动时绑定的监听地址列表。这里配置了两个协议:PLAINTEXT用于broker之间的数据传输和客户端连接,CONTROLLER专门用于Controller通信。advertised.listeners:这个参数是告诉客户端"你可以通过这个地址访问我"。在ZK模式下这个参数也很重要,尤其当broker部署在NAT环境或者Docker容器里的时候,很容易因为内外IP不一致导致连不上。我这里直接填的是节点自身的主机名和9092端口。controller.listener.names:指定Controller通信使用的是哪个监听器名称,这里就是CONTROLLER。listener.security.protocol.map:把监听器名称映射到具体的安全协议。CONTROLLER映射为PLAINTEXT,意思是Controller之间使用明文通信,不加密。inter.broker.listener.name:broker与broker之间通信使用的监听器名称。这里指定为PLAINTEXT,也就是走9092端口。
4.3 日志存储与持久化参数
log.dirs=/data/kafka-logslog.dirs指定Kafka日志数据存储目录。注意,这里不只是存放消息数据的目录,在KRaft模式下,它同时也会存放元数据日志。所以这个目录必须是本地的、独立的磁盘路径,不要和系统盘混在一起,否则IO竞争会很严重。三台机器上都需要先创建并授权这个目录:
sudo mkdir -p /data/kafka-logs sudo chown -R $(whoami):$(whoami) /data/kafka-logs另外一个重要参数是log.retention.hours,默认是168小时(7天),日志保留时间可以根据业务需求调整。比如测试环境可以缩短:
log.retention.hours=244.4 副本与ISR参数
num.partitions=3 default.replication.factor=3 min.insync.replicas=2num.partitions:新建Topic时默认分区数。这里是3,正好匹配三台broker,每台broker承载一个分区副本。default.replication.factor:新建Topic时默认副本数,3表示每个分区在三个broker上各保留一份副本。min.insync.replicas:生产端在配置了acks=all的情况下,写入请求至少要有几个副本处于同步状态才算成功。这里设置为2,意味着至少两台broker都确认写入后,才向生产者返回成功。这是保证数据不丢失的关键参数之一。
这三个参数的共同作用,决定了集群的容错能力。比如默认副本数为3,当一个broker宕机后,分区还剩两个副本,其中至少一个需要保持ISR状态,写入才能继续。如果min.insync.replicas设置成3,那任意一个broker宕机都会导致生产写入失败,这个要按业务可用性和数据可靠性做权衡。
以下是我实验环境中的完整配置,其它参数根据机器情况做了微调:
process.roles=broker,controller node.id=1 controller.quorum.voters=1@kafka1:9093,2@kafka2:9093,3@kafka3:9093 listeners=PLAINTEXT://kafka1:9092,CONTROLLER://kafka1:9093 advertised.listeners=PLAINTEXT://kafka1:9092 controller.listener.names=CONTROLLER listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL inter.broker.listener.name=PLAINTEXT log.dirs=/data/kafka-logs num.partitions=3 default.replication.factor=3 min.insync.replicas=2 log.retention.hours=24 offset.topic.replication.factor=3 transaction.state.log.replication.factor=3 transaction.state.log.min.isr=2offset.topic.replication.factor和transaction.state.log.replication.factor这两个参数也挺重要。消费者组的offset是存在一个内部Topic里的,事务状态日志也存在内部Topic里,这两个内部Topic的副本因子默认是3。如果集群规模比较小,只有一台或两台broker,得调小一点,否则内部Topic创建会失败。
5. 第一次启动前,如何正确生成集群ID并格式化存储目录
这一步是KRaft模式与ZK模式区别最大的地方,也是很多人最容易出错的地方。在ZK模式下,你启动Kafka之前不需要做任何"格式化"操作,ZK自己会管理好一切。但在KRaft模式下,你必须先为集群生成一个唯一的集群ID,然后用这个ID去格式化每个节点的存储目录,之后才能启动。
为什么需要这一步?因为KRaft模式下元数据日志是Kafka自己维护的,每个节点在启动之前必须知道自己属于哪个集群。如果集群ID不一致,节点之间无法加入同一个元数据日志复制组,整个集群就废了。
5.1 用官方工具生成集群ID
Kafka 2.8的bin目录下自带了一个工具,叫kafka-storage.sh。在任意一台机器上执行:
cd /opt/kafka/bin ./kafka-storage.sh random-uuid执行完会输出一串UUID,类似:
n7B6L0YvT1yH0NkFp3jC2w这个UUID就是你的集群ID。把它保存下来,后面每一步都会用到。注意,这个命令只需要执行一次,不管你有多少个节点,共享同一个集群ID。
5.2 格式化存储目录(关键步骤,不可跳过)
在每台机器上,用刚才生成的集群ID格式化各自的日志目录:
cd /opt/kafka/bin ./kafka-storage.sh format -t n7B6L0YvT1yH0NkFp3jC2w -c /opt/kafka/config/server.properties这条命令的作用是:读取server.properties里的log.dirs配置,在对应的目录下初始化元数据日志结构,并把集群ID写入其中。执行成功后,你会看到类似这样的输出:
Formatting /data/kafka-logs with metadata.version 2.8-IV0.这里有个非常关键的点:如果你之前用ZK模式跑过这套数据目录,里面已经存在旧的日志文件,那么格式化之前必须把目录清理干净,否则格式化会报错。我的做法是直接把目录清空:
sudo rm -rf /data/kafka-logs/*再强调一次:这一步必须三台机器都执行,而且格式化用的server.properties必须跟你后续启动用的完全一致。尤其是node.id、controller.quorum.voters、log.dirs这三个参数,如果格式化和启动时不一致,节点会直接拒绝启动,并报一些莫名其妙的数据目录错误。
5.3 格式化的本质:元数据日志的初始化
从底层来看,format命令做的事情就是在每个节点的日志目录下创建一个新的元数据日志分段,然后将集群ID和初始元数据快照写入其中。之后节点启动时,会读取这个目录,恢复自身的状态。
这也是为什么你不能用一个已经跑过数据的目录直接改成KRaft模式。因为旧目录里残留的ZK模式数据格式和新格式完全不同,Kafka无法自动迁移。2.8版本本身也不提供从ZK模式自动迁移到KRaft模式的工具(这个工具到3.x版本才逐渐成熟)。所以如果想把现有ZK集群迁移到KRaft,只能通过新建集群、拷贝数据的方式来做,或者直接等迁移工具完善后再操作。
6. 集群启动与验证:让三个节点真正工作起来
格式化完成之后,就可以启动Kafka集群了。顺序上没有严格要求,但从哪个节点开始会影响你观察日志的体验。我习惯从node1开始,因为它是第一个会尝试竞选Active Controller的节点。
6.1 启动节点
三台机器上都执行同样的启动命令:
cd /opt/kafka bin/kafka-server-start.sh config/server.properties如果想后台运行,用-daemon参数:
bin/kafka-server-start.sh -daemon config/server.properties启动过程中,观察日志输出。node1上应该能看到类似这些关键信息:
- 加载了格式化好的元数据日志
- 成功加入了Controller的投票集群
- 成为Active Controller或者等待其他Controller发起投票
- broker身份成功注册,监听在9092端口
如果一切正常,node1最终会输出类似:
[Controller id=1] Registering broker 1 at path /brokers/1node2和node3启动后,它们会通过controller.quorum.voters里配置的地址找到node1,然后以Controller和Broker的身份加入集群。这时候你再看node1的日志,应该能看到新的节点注册信息。
6.2 用Broker状态命令验证集群
Kafka提供了一个简单的工具来检查broker状态:
bin/kafka-broker-api-versions.sh --bootstrap-server kafka1:9092正常的话会列出每个broker支持的API版本,同时也就验证了broker之间的网络连通性和注册状态。
如果不方便执行命令,还可以用JMX端口看节点状态,但命令行方式是最直接的。
6.3 验证Controller的选举与元数据同步
要让集群真正可用,还需要创建一个测试Topic。在KRaft模式下,Topic的创建过程会触发一次元数据日志写入,这能顺便验证Controller的工作是否正常:
bin/kafka-topics.sh --bootstrap-server kafka1:9092 --create --topic test-topic --partitions 3 --replication-factor 3返回Created topic test-topic.之后,查看Topic详情:
bin/kafka-topics.sh --bootstrap-server kafka1:9092 --describe --topic test-topic正常输出应该显示3个分区,每个分区有3个副本,均匀分布在三个节点上。如果看到副本分布不均或某些分区Leader为空,就要检查是否某个节点没正常启动。
6.4 生产端和消费端全链路测试
验证消息读写,我用生产端脚本发几条消息:
bin/kafka-console-producer.sh --bootstrap-server kafka1:9092 --topic test-topic输入几条测试消息,然后开一个新的终端,用消费端脚本消费:
bin/kafka-console-consumer.sh --bootstrap-server kafka1:9092 --topic test-topic --from-beginning如果能正常看到刚才输入的消息,说明这个KRaft集群已经能完整支撑Kafka的读写链路了。到这一步,一个基本的集群部署就算成功了。
7. 生产环境部署与2.8版本常见问题排查
集群虽然跑起来了,但只能说"能用了"。生产部署还需要考虑很多东西,比如Controller和Broker要不要分离部署、安全协议要不要用TLS、JVM参数怎么调、日志持久化策略怎么定。同时,2.8版本里还有几个老坑,我把自己踩过的整理出来。
7.1 Controller节点数怎么选:奇数原则
Controller节点数量必须保持奇数。这是因为Controller之间的元数据投票协议要求大多数节点在线才能选出Active Controller并对外提供服务。3个Controller允许挂1个,5个Controller允许挂2个。偶数个Controller在理论上不会带来额外的容错能力,反而可能在某些特殊故障场景下导致选票分裂。所以生产环境选3个或者5个就够了,不要画蛇添足。
Controller和Broker是否分离,取决于你的业务规模。如果节点数量少、分区数少,Combined模式完全够用。但如果你管理的是超大规模集群,分区数几千甚至上万,建议把Controller节点独立开,避免元数据管理对数据读写造成资源竞争。Controller节点对磁盘IO、CPU的要求不高,但对延迟很敏感,所以给它独立的机器是更稳妥的选择。
7.2 2.8版本已知限制和兼容性问题
2.8的KRaft模式有不少限制需要提前知道:
第一,不支持kafka-configs.sh的动态配置修改。在2.8的KRaft模式下,有些动态配置项你改了不会实时生效,甚至有些配置操作直接被拒绝。这个到3.x版本才逐步完善。所以2.8更适合学习和验证,不适合大规模生产。
第二,不支持kafka-reassign-partitions.sh的分区副本迁移。这个工具在ZK模式下是日常运维法宝,但KRaft模式的2.8版本里它还不能正常工作,或者说不具备完整的操作能力。如果你需要迁移分区,会比较麻烦。
第三,KRaft模式下的Controller节点目前不支持滚动升级到新版本。你在2.8上用KRaft,后续想升级到2.9或者3.x,官方给出的建议是先重建集群再迁移数据。这对生产来说是一个很大的迁移成本,我当初评估完直接放弃了在线上用2.8的想法。
第四,sasl认证和ssl加密在2.8 KRaft模式下支持不完整。Controller之间的通信虽然可以走TLS,但配置比较复杂,而且官方文档在某些场景下还标注了已知问题。如果你的安全要求很高,建议不要在生产环境使用2.8的KRaft。
7.3 排查“节点无法启动”的几个高危错误
我搭建过程中遇到最典型的问题是格式化之后的目录出现了残留信息。比如我第一次在node2上格式化后,发现日志报错说metadata版本不对,最后发现是我在node2上用了别的server.properties文件格式化,导致元数据版本和集群ID都对不上。解决办法就是清空目录重新格式化。
另一个高频问题是端口冲突。9092和9093两个端口都要确保没有被占用。用以下命令检查:
ss -lntp | grep -E "9092|9093"如果端口被占用,改server.properties里的监听端口,同时记得更新advertised.listeners和controller.quorum.voters里的地址。
还有一个隐蔽的问题:如果你在配置里把advertised.listeners写成了localhost或者127.0.0.1,客户端和broker之间无法正确建立连接。很多新手在这个地方卡住,明明broker显示启动了,但客户端连接就是超时。我建议,只要涉及跨节点通信,一律使用可解析的主机名或者内网IP。
7.4 Controller组和Broker组全部分离的部署思路
如果你的机器充足,可以考虑将Controller和Broker分离部署,这也是生产环境推荐的架构形态。假设用3台Controller节点和3台Broker节点:
Controller节点(运行在独立机器上):
process.roles=controller node.id=1 controller.quorum.voters=1@ctrl1:9093,2@ctrl2:9093,3@ctrl3:9093 listeners=CONTROLLER://ctrl1:9093 controller.listener.names=CONTROLLER listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL log.dirs=/data/kafka-metadataBroker节点(运行在另外的机器上):
process.roles=broker node.id=4 controller.quorum.voters=1@ctrl1:9093,2@ctrl2:9093,3@ctrl3:9093 listeners=PLAINTEXT://broker1:9092 advertised.listeners=PLAINTEXT://broker1:9092 controller.listener.names=CONTROLLER inter.broker.listener.name=PLAINTEXT log.dirs=/data/kafka-logs注意Broker节点上没有controller.listener.names对应的监听器,但依然需要配置这个参数,因为broker需要通过该监听器名称去理解Controller返回的元数据。同时,broker上的controller.quorum.voters必须和Controller节点上的完全一致,否则broker无法找到Controller。
分离部署的核心思路是:Controller节点之间通过9093端口互通,Broker节点通过9092端口为客户端提供服务,Broker与Controller之间的通信走9093端口。三个端口职责清晰,互不干扰。
8. 监控与运维层面:2.8 KRaft模式下要怎么管
部署完集群只是第一步,日常运维才是重头戏。在这里分享一下监控和运维层面的体会。
8.1 从ZooKeeper监控转型到Controller监控
在ZK模式下,你要监控ZooKeeper集群的/brokers/ids路径、ZK的延迟、会话数量等。在KRaft模式下,这些都不需要了。你要转而关注Controller节点的元数据日志复制状态、Active Controller是否稳定、各broker节点的元数据版本是否滞后。
在2.8中,Controller的状态信息可以通过JMX指标获取。主要包括:
kafka.controller:type=KafkaController,name=ActiveControllerCount:当前节点是否为Active Controllerkafka.controller:type=KafkaController,name=OfflinePartitionsCount:离线分区数kafka.server:type=BrokerTopicMetrics,name=BytesInPerSec:broker的入站流量
这些都是原来就有的指标,但监控对象从ZK换成了Controller节点本身。如果你的监控系统原来有一堆ZK相关告警,要记得删除或调整规则。
8.2 元数据日志目录的容量规划
KRaft模式下,元数据日志会随着集群变更不断增长。虽然单条元数据记录很小,但在频繁创建Topic、修改配置、broker上下线的集群里,日志增长还是可观的。
建议为元数据日志目录配置足够的磁盘空间,并设置监控告警。log.dirs目录下如果元数据日志占用空间过大,一个是可以考虑定期清理旧的元数据日志段文件,另一个是在确认集群稳定后,通过调整元数据日志保留策略来控制增长速度。
8.3 节点下线与上线操作
在KRaft模式下,下线一个broker节点前,最好确认它的分区副本已经被迁移到其它节点。2.8版本中,由于kafka-reassign-partitions.sh存在功能缺失,你可能需要手动检查分区分布,确保该节点上的所有分区都有足够的副本在其他节点上。
上线新broker节点时,在server.properties中对node.id分配一个从未使用过的新ID,格式化数据目录后启动即可。新broker会自动从Controller拉取全量元数据快照,然后开始接收分区副本的复制任务。
9. 一次真实的排障记录:advertised.listeners配错之后的诡异现象
最后分享一个我自己在搭建过程中真实遇到的排障过程,这个问题在2.8 KRaft模式下特别容易遇到,而且它不报明显错误,排查起来很费劲。
当时我把三台节点的Kafka都启动成功了,broker状态也都能看到。但是在用kafka-console-producer.sh往test-topic里写入消息时,生产者一直显示超时。看日志,报错信息是关于"Connection to node -1 could not be established. Broker may not be available."。
我本能地怀疑是防火墙问题。但检查了三台机器的防火墙、安全组,都没有拦截9092端口。用telnet测试端口也能通。
后来我仔细看了一遍每台机器的server.properties,发现node2上我把advertised.listeners写成了PLAINTEXT://192.168.10.12:9092,而listeners写的是PLAINTEXT://kafka2:9092。理论上这样配置也能工作,但因为Kafka在解析主机名kafka2的时候,解析到了127.0.0.1这个回环地址,导致broker之间互相通信时,node1尝试用kafka2去连接node2,却连到了自己身上。整个集群的broker间通信就乱了。
这给了我一个教训:在生产环境里,advertised.listeners必须填入客户端和broker都能稳定访问的地址,最好是内网固定IP,避免依赖DNS解析结果和主机名映射。如果你用主机名,一定要确保/etc/hosts里配置正确,并且在每个节点都保持一致的解析结果。
修改完node2上的配置,重启之后,问题立刻消失。这种问题在ZK模式下同样存在,但KRaft模式下因为节点角色变多、端口变多,排查起来更容易混乱。所以我的建议是:配置阶段就把所有监听地址固定成IP,不要图省事用localhost,也别依赖系统DNS的解析顺序。
10. 我个人对2.8 KRaft模式的定位建议
最后说一点我的个人判断。Kafka 2.8的KRaft模式是一个里程碑式的版本,它第一次让用户能够在不部署ZooKeeper的情况下跑起一套Kafka集群。它对学习Kafka内部原理、验证KRaft架构设计有非常大的价值。
但如果你要跑关键生产业务,我劝你暂时不要上2.8。原因前面也提到了,2.8的KRaft缺少一些运维工具支持,迁移工具也不完善,安全特性支持不到位。更适合的选择是等待3.3之后的成熟版本,或者直接用3.4、3.5这些KRaft已经生产就绪的版本。如果确实必须在早期版本上跑KRaft,建议先压测、做好故障演练,至少不要在生产环境裸奔。
搭完这套2.8集群之后,我对Kafka架构演进的理解提升了一个档次。以前看Kafka源码,总是要关心ZooKeeper怎么协调、Controller怎么选主、broker怎么注册,现在再看KRaft,感觉到Kafka真的在朝着"自我管理、自我调度"的方向走。省去的不仅是一个组件,而是一整套运维心智负担。这篇文章把部署过程中的关键节点都梳理了一遍,如果你也在折腾2.8的KRaft模式,希望能帮你少踩几个坑。