Kafka 2.8 发布至今仍被不少人提起不是因为新增了多少业务功能而是它第一次正式提供了去除 Zookeeper 的部署模式——KRaft。很多团队因为这个特性开始折腾无 ZK 集群但实际落地时发现配置文件变了、启动流程变了、连元数据管理逻辑都得重新理解照着老教程写必然踩坑。这篇内容就是我基于真实环境反复试错后的完整记录从架构原理讲到三节点集群的配置细节再到常见故障的排查思路争取让你照着走一遍就能跑起来省掉自己趟雷的时间。1. 无 Zookeeper 模式到底改了什么1.1 为什么一定要甩掉 Zookeeper先聊一个很现实的问题Zookeeper 在 Kafka 里到底干什么活为什么这么多人想把它换掉。Kafka 的元数据——包括主题列表、分区分配关系、副本状态、配置信息、消费组进度——长期以来都存放在 Zookeeper 中。Broker 启动时要向 ZK 注册自己控制器Controller的选举也依赖 ZK 的临时节点和 Watch 机制消费者组协调器同样需要 ZK 来维护成员状态。可以说 ZK 是 Kafka 的大脑和神经中枢但它和 Kafka 是两套独立的系统部署时要单独维护一套集群版本兼容性要小心对齐ZK 本身又有一套选举机制和会话超时逻辑一旦出现网络抖动或 GC 停顿就容易引发脑裂或者元数据不一致的问题。大概在 2020 年前后Kafka 社区启动了 KIP-500 项目目标就是把 Zookeeper 从 Kafka 的架构中彻底移除改用 Kafka 内置的 Raft 协议来管理元数据。Kafka 2.8 是第一个把 KRaft 模式作为早期预览版放出来的版本虽然当时官方明确标注了是 early access但已经是里程碑式的变化——毕竟这意味着你可以在完全无 ZK 的情况下跑起一套 Kafka 集群。1.2 KRaft 模式下的角色划分KRaft 模式下Kafka 自己承担了元数据存储和一致性保证的工作。节点角色被重新划分为两类Controller控制器负责管理元数据维护主题、分区、配置、ISR 等信息并通过 Raft 协议在多个 Controller 节点之间达成共识。Controller 有三节点或五节点的高可用部署方式担任 Leader 的节点响应外部请求。Broker代理负责处理生产消费请求存储消息数据和之前版本里的 Broker 职责基本一致。Broker 节点会从 Controller 那边拉取元数据更新本地缓存一份元数据信息。比较关键的一点是一个进程可以同时承担 Broker 和 Controller 两种角色这种模式叫 combined 模式适合节点数量有限的测试环境。生产环境更推荐把 Controller 和 Broker 分开部署也就是 dedicated 模式这样 Controller 的性能不会被数据读写路径干扰故障域也更清晰。1.3 版本选择的现实考量项目标题写的是 Kafka 2.8这里必须提醒一句2.8 的 KRaft 只是 Preview 级别功能不太完整官方文档里也明确说了“not recommended for production”。比如在 2.8 早期版本变更分区副本、调整 ISR 之类的管理员操作还有不少限制Controller 和 Broker 之间的协议也在快速迭代。如果你是自己在测试环境折腾想体验无 ZK 模式Kafka 2.8 完全够用了而且很多经典配置参数从 2.8 开始才稳定成型。如果是要评估生产落地建议直接上 3.5 以上的版本KRaft 在 3.3 之后已经能够用于生产3.5 开始对 Controller 迁移、动态重新配置这些功能支持得比较完善。这篇博文的配置方法在 2.8~3.x 之间基本通用差异我会在相关章节说明。2. 集群规划与前置环境准备2.1 节点规划与角色分配我这次搭建的是一个三节点集群考虑到测试目的采用 combined 模式三个节点同时承担 Controller 和 Broker 角色。对于 KRaft 模式Controller 节点数必须是奇数个三节点可以容忍一个节点故障生产环境如果条件允许建议用五个 Controller 节点。假设计划使用三台服务器内网 IP 分别是 10.0.0.11、10.0.0.12、10.0.0.13这里用 A、B、C 作为虚拟节点名替代真实主机名。每个节点的处理角色都是 brokercontrollernode.id 分别设置为 1、2、3。端口规划方面Broker 对外服务端口用 9092PLAINTEXTController 内部通信端口用 9093CONTROLLER。9093 这个端口不需要暴露给客户端它是 Controller 之间通过 Raft 协议同步元数据的通道也是 Broker 节点向 Controller 拉取元数据的通道所以建议走内网不要暴露公网。2.2 JDK 与 Kafka 安装Kafka 2.8 要求 JDK 8 以上我实测在 JDK 8 和 JDK 11 下跑 KRaft 模式都没有问题推荐用 JDK 11长期支持且 GC 表现更稳定。安装步骤不复杂但有一些细节要提醒。# 以 root 用户或具有 sudo 权限的用户执行 mkdir -p /opt/kafka tar -zxvf kafka_2.13-2.8.1.tgz -C /opt/kafka # 创建软链接方便版本切换 ln -s /opt/kafka/kafka_2.13-2.8.1 /opt/kafka/current # 配置环境变量 cat /etc/profile EOF export KAFKA_HOME/opt/kafka/current export PATH$PATH:$KAFKA_HOME/bin EOF source /etc/profile注意选择以2.13开头的发行包这是 Scala 编译版本2.13 对于 Kafka 2.8 来说是官方推荐的编译版本功能和兼容性有保障。安装完成后运行kafka-topics.sh --version确认版本号正常显示为 2.8.1。2.3 目录规划与系统参数调优数据目录建议单独挂载一块数据盘不要放在系统盘上避免系统日志和消息数据争抢磁盘 IO。规划目录如下# 数据目录 mkdir -p /data/kafka/data mkdir -p /data/kafka/meta # 日志目录运行日志非数据日志 mkdir -p /var/log/kafka其中/data/kafka/data是消息数据目录对应配置文件的log.dirs/data/kafka/meta是 KRaft 元数据目录对应metadata.log.dir。在 KRaft 模式推出以前只有一个数据目录现在多了metadata.log.dir这个目录存放的是 Raft 日志和快照数据是整个集群一致性的基石重要性不比消息数据低必须放在可靠的磁盘上。系统参数上有几个值得提前检查的配置项# 检查文件句柄限制 ulimit -n # 建议设置 cat /etc/security/limits.conf EOF * soft nofile 1000000 * hard nofile 1000000 * soft nproc 65535 * hard nproc 65535 EOF # 关闭交换分区影响视情况设置 sysctl -w vm.swappiness10文件句柄调大是因为 Kafka 每个分区会打开大量文件句柄分区数量多的时候很容易撞到默认的 1024 限制。vm.swappiness调到 10 左右是为了避免 CTL 触发高 GC 导致 Controller 节点短暂失联。这些都不是 KRaft 特有的要求但既然搭新集群一次性配置到位总归省事。3. 核心配置文件逐项解析3.1 server.properties 总览KRaft 模式下的config/server.properties和旧版有很大不同最大的变化就是新增了process.roles、node.id、controller.quorum.voters这几个核心参数同时删掉了zookeeper.connect。我先把三个节点共用的配置骨架列出来然后再逐个解释关键项。# 节点角色 process.rolesbroker,controller node.id1 controller.quorum.voters110.0.0.11:9093,210.0.0.12:9093,310.0.0.13:9093 # 监听器 listenersPLAINTEXT://10.0.0.11:9092,CONTROLLER://10.0.0.11:9093 advertised.listenersPLAINTEXT://10.0.0.11:9092 controller.listener.namesCONTROLLER inter.broker.listener.namePLAINTEXT # 数据目录 log.dirs/data/kafka/data metadata.log.dir/data/kafka/meta # 其他常规配置 num.partitions3 default.replication.factor2 offsets.topic.replication.factor2 transaction.state.log.replication.factor2 transaction.state.log.min.isr1 log.retention.hours168 log.segment.bytes1073741824 log.retention.check.interval.ms300000 auto.create.topics.enablefalse有三个节点需要改的只有node.id、listeners、advertised.listeners和controller.quorum.voters里对应的 IP 地址其余保持一致就好。我强烈建议把这一份配置保存成模板逐个节点改差异项避免漏改或写错。3.2 关键参数的作用与选值逻辑逐个说说这些参数背后的逻辑你理解了之后就不会瞎填。process.roles取值有三种broker、controller、broker,controller。因为我们用的是 combined 模式所以写broker,controller。如果将来扩容专门加一台 Controller 节点那台机器就只写controller。node.id是每个节点的唯一标识注意它替代了旧版的broker.id在 KRaft 模式下所有节点——包括专属 Controller——都需要有唯一的node.id范围从 1 开始递增就行。controller.quorum.voters是 KRaft 最核心的一个参数格式是idhost:port用逗号分隔罗列所有 Controller 节点。每个节点启动时会把自己配置的 voters 列表和集群实际状态做对比如果发现自己不在这个列表里会被拒绝加入。这个参数在三台机器上必须完全一致尤其不能漏节点或写错端口。listeners和controller.listener.names要配套理解。listeners里定义了多个协议的监听地址其中PLAINTEXT用于客户端和 Broker 间数据通信CONTROLLER用于 Controller 间 Raft 通信。controller.listener.names指定哪些 listener 是 Controller 通信使用的必须对应listeners里定义的协议名。在你的场景中就是CONTROLLER。advertised.listeners是给客户端用的连接信息。如果你用localhost测试单机可以直接写成PLAINTEXT://localhost:9092如果是多节点集群必须填对客户端能访问的 IP 或域名比如PLAINTEXT://10.0.0.11:9092。这个配置错了会出现“客户端连接成功但消息一直发不出去”的诡异现象。metadata.log.dir是 KRaft 专用的元数据目录这点容易忽略。旧版 Kafka 没有这个参数早期 KRaft 版本叫meta.properties所在的目录后来明确为metadata.log.dir。如果你的 Kafka 2.8.1 报错说找不到该参数可以先检查是不是版本太老2.8.0 早期版本对这个参数的支持还不够完整。3.3 安全与认证的占位说明上面配置里用的都是PLAINTEXT也就是无加密、无认证的明文通信。生产环境绝不能这么干至少要启用 SASL_SSL 或者 SSL这一点无论是 ZK 模式还是 KRaft 模式都一样。KRaft 模式下认证配置的位置发生了迁移旧版在zookeeper.set.acl那套体系里配置新版本变成了controller.quorum.*相关配置例如controller.quorum.ssl.*或controller.quorum.sasl.*这些前缀。如果将来要加认证配置逻辑是Broker 之间走listener.name.sasl_ssl.*Controller 之间走controller.quorum.sasl.*两套机制独立配置、互不干扰。4. 集群初始化与启动全过程4.1 生成集群 ID 与格式化存储目录KRaft 模式和 ZK 模式最大的操作差异就在这里启动之前必须先执行一次格式化生成集群元数据。格式化会写入一个唯一的集群 ID 到元数据目录中所有节点必须使用同一个集群 ID否则节点之间无法组成集群。# 在一台节点上生成 UUID KAFKA_CLUSTER_ID$(bin/kafka-storage.sh random-uuid) echo $KAFKA_CLUSTER_ID执行后得到的是一串形如e6XzZFqPRNCF6XsmU5_NKA的随机 ID。拿到这个 ID 之后在每个节点上执行格式化。# 在 A、B、C 三台节点上依次执行 bin/kafka-storage.sh format -t e6XzZFqPRNCF6XsmU5_NKA -c config/server.properties格式化成功后/data/kafka/meta目录下会生成meta.properties文件里面记录了cluster.id和node.id可以通过cat检查确认。这里有一个必须注意的坑如果某个节点没有加入controller.quorum.voters列表格式化时该节点会因为校验不过而报错。我碰到过一次把voters里的 IP 写错导致格式化失败的情况报错信息还比较隐晦只有自己仔细核对配置才能发现。另外format命令会清空元数据目录执行前要确认不会误删已有数据。当然对全新集群来说一般不存在这个问题。4.2 启动 Controller 与 Broker格式化完成后就可以启动服务了。如果按照旧习惯直接用kafka-server-start.sh启动也行现在它仍然是最常用方式但新版也提供了职责明确的第一代工具命令。# 在 A、B、C 三个节点上依次启动 bin/kafka-server-start.sh -daemon config/server.properties进程是否起来可以从日志确认tail -f logs/server.log启动日志里重点关注两个输出Kafka Server started表示 Broker 启动完成Controller server started则表示当前节点成功参与了 Controller 角色。考虑到我们使用的是 combined 模式同一进程会异步启动两部分功能所以两个日志都会出现。注意如果启动顺序不对比如刚开始只有 A 节点启动它会在日志里反复打印类似Failed to connect to controller或者Cannot connect的提示这个不用担心等 B、C 节点启动到位后会自动恢复正常。Raft 协议本身就能容忍这种启动顺序问题无需手动干预。4.3 创建主题与基本验证集群启动后先创建一个测试主题验证整体链路。bin/kafka-topics.sh --bootstrap-server 10.0.0.11:9092 --create \ --topic krft-test \ --partitions 3 --replication-factor 2创建成功后可以通过describe命令确认分区的副本分配情况bin/kafka-topics.sh --bootstrap-server 10.0.0.11:9092 --describe --topic krft-test输出会类似Topic: krft-test PartitionCount: 3 ReplicationFactor: 2 Topic: krft-test Partition: 0 Leader: 1 Replicas: 1,2 Isr: 1,2 Topic: krft-test Partition: 1 Leader: 2 Replicas: 2,3 Isr: 2,3 Topic: krft-test Partition: 2 Leader: 3 Replicas: 3,1 Isr: 3,1Leader 和 Isr 都正常列出说明节点间通信没问题。如果这里出现Leader: -1或者 ISR 列表为空大概率是 Controller 选举出了问题这个我在后面的排查章节详细展开。最后用生产消费测试确认端到端可用# 终端1消费 bin/kafka-console-consumer.sh --bootstrap-server 10.0.0.11:9092 \ --topic krft-test --from-beginning # 终端2生产 bin/kafka-console-producer.sh --bootstrap-server 10.0.0.11:9092 \ --topic krft-test5. 故障转移与高可用实测5.1 停掉一台节点验证自动恢复集群搭好了最关心的事情就是某台节点挂了之后集群还能不能正常工作。先看一下当前 Controller 是哪个节点。在任意 Broker 上执行bin/kafka-metadata-quorum.sh --bootstrap-server 10.0.0.11:9092 describe --status输出里会显示当前 Leader 是哪个 node.id。因为这个集群用的是 combined 模式每个节点都可能成为 Controller Leader。假设当前 Controller Leader 是节点 A接下来我停掉节点 B 来验证故障转移。注意停掉非当前 Leader 的节点是为了验证在正常集群压下负载时副本重新分配和消息生产消费是否能正常存活。# 在节点B上 bin/kafka-server-stop.sh等待几秒后再次查看描述bin/kafka-topics.sh --bootstrap-server 10.0.0.11:9092 --describe --topic krft-test你会发现 B 节点原本作为 Leader 的分区比如 partition 1自动切换到其他节点的副本Isr 列表里也移除了 B 节点。同时再执行一次kafka-metadata-quorum.sh describe --statusController Leader 应该转移到了 A 或 C。整个过程无需人工干预Raft 协议会自行完成 Leader 再选举。如果你在生产消费测试中发现消息仍然正常收发说明故障转移已经生效。5.2 重启节点与数据同步验证把 B 节点重新启动后它会重新加入集群。观察日志会看到它正在从其他节点拷贝缺失的数据副本一段时间后describe输出中 B 节点又回到 ISR 列表。这个“追赶”过程不需要手动触发Controller 会自动将 B 标记为Online并同步增量数据。一个需要留意的细节是KRaft 模式下的元数据同步和旧版不同它不走 ZK Watch而是基于 Raft 日志的快照和增量推送。这意味着 Controller 和 Broker 之间会保持一条长连接Broker 定期向 Controller 发送 FollowerFetchRequest 来拉取元数据变更。如果节点之间的网络非常不稳定可能导致元数据长期不同步表现就是主题创建了但某些节点看不到。5.3 保留测试结论后的思考实测下来KRaft 模式在三节点 combined 部署下稳态表现和故障转移能力是及格的。只要 Controller 节点保持多数在线三节点中至少两个就能持续对外提供元数据服务。不过必须提醒的是如果 Controller 三个节点挂了两个整个集群会进入只读或拒绝服务状态——因为元数据无法更新任何创建主题、修改分区、消费组协调等操作都会失败已存在于各 Broker 上的老分区数据还可以被读取但新写入也会因为协调器不可用而受影响。旧版 ZK 模式里ZK 集群挂了同样会导致集群不可用。这个风险点没有本质变化所以架构设计时还是要把 Controller 节点故障域尽量拉开。6. 从 Zookeeper 模式迁移到 KRaft 的路径6.1 迁移工具与限制如果你已经有一套跑在 ZK 模式下的 Kafka 集群想迁到 KRaft 模式Kafka 提供了一条官方支持的工具路径从某个版本开始可以用kafka-storage.sh附带的方式做集群级元数据迁移。不过在 2.8 这个早期版本里迁移工具还不成熟官方一度建议直接“新集群 数据重放”这也可能是很多人选择直接用最新稳定版的理由。突出一点不管工具成熟度如何线上直接原地切换是不推荐的。KRaft 和 ZK 模式的元数据模型、Broker ID 管理、日志存储格式都不完全一致原地迁移的风险远高于新老集群并行切换。如果你只是学习体验我的建议是直接起一套全新的 KRaft 集群不折腾迁移。迁移问题等升级到 3.5 版本再规划会更轻松那时候的迁移工具已经能够处理不少边界情况。6.2 多环境并行与流量切换比较稳妥的做法是搭建一套独立的 KRaft 环境把生产环境的流量通过双写或复制工具同步过去观察稳定后再逐步切读。这个思路在 Kafka 社区不算新鲜只是执行时要特别注意消费组位点的迁移必须在切换前尽量拉平新旧集群的位点差否则会出现消费者从头消费大量积压数据的情况。对于个人实验来说没必要搞这么复杂。用新环境跑跑生产消费、测测节点故障转移、验证一下控制台工具的兼容性这些已经足够帮你建立对 KRaft 的直观认知了。7. 常见问题与排查技巧实录7.1 节点无法加入集群症状新节点启动后日志中反复提示Failed to find current epoch或Connection to node X failed集群元数据在这个节点上始终无法同步。排查思路按顺序来先确认三个节点的controller.quorum.voters是否完全一致尤其是 IP 和端口。再确认node.id与其他节点是否冲突每个节点必须唯一。然后确认metadata.log.dir下的meta.properties里的cluster.id是否一致不一致说明格式化时用的 UUID 不一样必须重新格式化。这里最容易踩的坑就是格式化时忘了把KAFKA_CLUSTER_ID写成同一个值三个节点各生成各的结果永远组不成集群。格式化命令建议只在一台节点上生成 UUID其他节点直接沿用。7.2 Controller 无法选举 Leader症状kafka-metadata-quorum.sh describe --status输出的 Leader 一直是空或者状态一直停留在Transitioning。可能原因有这么几类Controller 之间的端口不通。检查 9093 端口是否被防火墙拦截或者监听地址是否设置了localhost。节点时钟不同步。Raft 协议依赖时间比较 LogIndex 和 Term如果三台节点时钟差太多选举会处于混乱状态。建议所有节点配置 NTP 时间同步。Voters 列表缺失节点。比如只写了两个节点第三台虽然启动了但不在仲裁集里永远无法参与选举。时间同步这一点以前 ZK 模式也有要求但在 KRaft 下更为敏感因为所有元数据写入都要通过 Raft 达成多数一致而 Term 和 LogIndex 的推进依赖于单调递增的时钟逻辑。7.3 客户端连接正常但无法收发消息症状客户端能用--bootstrap-server连上集群创建主题列表也能看到但生产和消费时经常超时或报Leader not available。这个大概率与advertised.listeners配置有关。客户端先从 Broker 获取元数据拿到的是每个分区的 Leader 节点地址这些地址来自advertised.listeners。如果你填的是localhost:9092客户端从别的机器访问时就会尝试连localhost自然连不通。解决方案很简单advertised.listeners必须填客户端真正能访问到的 IP 或域名不要偷懒。7.4 元数据目录损坏怎么办如果/data/kafka/meta目录损坏或节点意外断电导致元数据目录状态异常节点启动时会拒绝继续服务日志提示类似Recovery failed的信息。首先要判断这个节点在集群中的角色。如果只是普通 Brokercombined 模式下所有节点都参与元数据所以更麻烦最简方案是把该节点的元数据目录清掉重新格式化然后让它重新加入集群。因为消息数据目录/data/kafka/data里保存的分区数据还在启动后该节点会以“跟进者”身份重新从现有集群同步元数据分区数据会做增量追赶不需要全量重放。当然前提是消息数据目录没有损坏否则只能在该主题副本允许的情况下把它从集群中移除再重新加回来。这个技巧对 ZK 模式完全无效因为 ZK 模式下元数据在 ZK 集群里Broker 只是数据节点。KRaft 模式下元数据在每个 Controller 节点本地都有一份所以单个节点的元数据损坏是可以自愈的这也算是它架构上的一个优势。7.5 日志中的常见关键信息排查问题时看一眼日志里的这几行会有很大帮助日志关键词含义Registered brokerBroker 成功注册到元数据层Controller server started当前进程已完成 Controller 角色初始化Kafka Server startedBroker 服务完全启动Failed to connect to controller启动初期正常的重连提示持续出现则异常Unable to connect to metadata元数据通信异常检查网络和 listener 配置8. 部署过程中值得留意的几个细节8.1 日志目录别和数据目录混用有人图省事把logs运行日志和消息数据放到同一个目录短期看着没事一旦分区数量上去了日志文件疯狂增长会挤占磁盘空间导致消息写入失败。建议独立目录存放运行日志并配置日志轮转。Kafka 自带的log4j.properties默认有按大小滚动策略但保留份数建议确认一下避免日志无限积累。8.2 监控 Controller 节点的 Raft 指标KRaft 模式下Controller 节点的 Raft 状态是集群健康的核心指标。Kafka 暴露了若干 JMX 指标例如kafka.controller:typeKafkaController,nameActiveControllerCount、kafka.server:typeraft-metrics之类的指标。如果你搭过 Prometheus 监控建议重点盯这几个ActiveControllerCount正常值为 1大于 1 说明脑裂小于 1 说明没有 Controller 可用。元数据日志同步延迟单位通常是毫秒持续增长说明网络或磁盘有问题。未同步副本数量在 KRaft 模式下对应的是元数据分区 ISR 收缩情况。其实哪怕没有监控系统只要定期手动跑一次kafka-metadata-quorum.sh describe --status确认 Controller 正常、ISR 完整就已经能防住大部分风险了。8.3 节点扩容时怎么操作KRaft 模式下扩容比 ZK 模式简单因为不需要额外搭 ZK 节点。假设当前三节点集群要扩成五节点新节点按照相同配置修改node.id加入listeners。在所有现有节点和新节点的controller.quorum.voters中加入新节点信息。依次重启现有节点使配置生效然后启动新节点。新节点会开始同步元数据日志。注意因为要改controller.quorum.voters所以现有节点需要滚动重启顺序上建议从非当前 Controller Leader 的节点开始最后再动 Leader把对读写服务的影响降到最低。8.4 删数据千万别删错目录由于 KRaft 模式多了一个metadata.log.dir一些小伙伴在清理磁盘时容易误把元数据目录当成普通日志清掉结果节点启动直接崩溃。牢记/data/kafka/meta和/data/kafka/data都属于 Kafka 的核心数据删除任何一个都会导致节点状态异常。运行日志放/var/log/kafka那是唯一可以放心清理的目录。9. 我的最终体验总结Kafka 2.8 的 KRaft 模式虽然只是个 preview但它验证了一条可行的路径没有 ZookeeperKafka 依然能跑得很稳甚至元数据管理更直接、组件更少。如果你时间有限建议先在单机或三节点测试环境里把这个模式摸熟等你要面对生产集群时会发现那些踩坑经验全部能复用。从实际体验来说KRaft 模式最让我惊喜的不是性能提升说实话单论吞吐和老版本差别不大而是运维心智负担的下降——不用再维护一套 ZK 集群不用再担心 ZK 和 Kafka 版本兼容问题故障排查链路直接从两套系统简化成一套。光这一点就足够让这个模式成为 Kafka 未来几年的主航道。配置方面再分享一个小习惯每台节点的server.properties我会用一个脚本按模板自动生成只允许变量部分的差异避免手改导致的低级错误。另外强烈建议把KAFKA_CLUSTER_ID写进部署文档里保存集群初始化阶段一旦丢了它所有节点都得重新格式化那可真是一场灾难。这篇内容到这里就结束了。如果你搭建过程中遇到什么问题欢迎在评论区把日志贴出来一起讨论我看到了都会回复。
阅读完成 · 觉得有帮助?