Broker 没挂,磁盘也没满,生产者却一直报NOT_LEADER_OR_FOLLOWER。 这种问题我一般不先看业务代码,先看 Kafka 控制面。老版本 Kafka 里,这个控制面背后站着的就是 ZooKeeper。
Kafka 里的 ZooKeeper,别把它想成“存消息的地方”。
消息不在 ZooKeeper。
消息在 Broker 的日志文件里,路径一般类似这样:
/data/kafka-logs/order_pay-3/00000000000000000000.log
/data/kafka-logs/order_pay-3/00000000000000000000.index
ZooKeeper 管的是另一摊事:谁还活着,谁是 Controller,Topic 有哪些分区,分区副本在哪些 Broker 上,哪个副本是 Leader。
老版本配置里一般能看到这一行:
broker.id=2
listeners=PLAINTEXT://10.10.8.22:9092
log.dirs=/data/kafka-logs
zookeeper.connect=zk-1:2181,zk-2:2181,zk-3:2181/kafka-prod
zookeeper.session.timeout.ms=18000
看到zookeeper.connect,基本就能判断这是 ZK 模式的 Kafka 集群。
Broker 启动后,会去 ZooKeeper 上注册一个临时节点。临时节点这东西挺关键,Broker 活着,节点就在;Broker 进程挂了,或者和 ZooKeeper 的 session 断了,节点就会消失。
线上看 Broker 是否还在,不能只看进程。
public class KafkaZkProbe {
private final ZooKeeper zk;
public KafkaZkProbe(String connect) throws Exception {
this.zk = new ZooKeeper(connect, 8000, e -> {
System.out.println("zk event: " + e.getState() + ", " + e.getType());
});
}
public void printClusterView() throws Exception {
System.out.println("brokers:");
for (String id : zk.getChildren("/brokers/ids", false)) {
byte[] raw = zk.getData("/brokers/ids/" + id, false, null);
System.out.println("broker-" + id + " => " + new String(raw));
}
byte[] controller = zk.getData("/controller", false, null);
System.out.println("controller => " + new String(controller));
}
}
这段代码不是让你在线上服务里依赖 ZooKeeper。真要排障,临时跑一下可以。尤其是怀疑 Broker 假活、Controller 频繁切换的时候,比盯着业务日志靠谱。
ZooKeeper 在 Kafka 里最重要的一件事,是选 Controller。
Controller 不是业务意义上的 Controller,它是 Kafka 集群里的“调度员”。哪个分区的 Leader 挂了,哪个副本要升上来,Topic 创建后分区怎么分配,这些活都归它管。
比如一个 Topic:
topic: order_pay
partition: 0
replicas: [1, 2, 3]
leader: 1
isr: [1, 2, 3]
如果 Broker 1 掉了,Controller 要从 ISR 里挑一个新的 Leader,比如 Broker 2。这个动作不是生产者自己决定的,也不是消费者决定的,是 Controller 根据元数据和副本状态推进的。
所以 ZooKeeper 抖一下,最烦的不是“ZooKeeper 抖了”这句话本身,而是后面一串连锁反应:
[Controller id=2] Broker 1 was removed from live brokers
[Controller id=2] New leader and ISR for partition order_pay-0 is Leader:2, ISR:[2,3]
[Producer clientId=pay-producer] Received NOT_LEADER_OR_FOLLOWER
这时候业务侧看到的可能只是发送失败、消费延迟、偶发超时。你不顺着 Controller 日志看,很容易去改一堆没用的重试参数。
ZooKeeper 还保存 Kafka 的一些元数据。
比如 Topic、分区、副本分布、部分配置、ACL 等。注意这里也有版本差异,老 Consumer 的 offset 曾经放 ZooKeeper,后来新 Consumer 早就放到 Kafka 内部 Topic__consumer_offsets里了。这个点别背错,不然一问 offset 存哪儿就露馅。
我以前查过一次消费位点异常,有人上来就说“去 ZooKeeper 查 offset”。我第一眼就不太信。看客户端版本和 group 协议,发现是新 Consumer,那就应该去查 Kafka:
bin/kafka-consumer-groups.sh \
--bootstrap-server 10.10.8.21:9092 \
--group pay-sync-group \
--describe
不要什么都往 ZooKeeper 上扣。
ZooKeeper 对 Kafka 的价值,核心就四个字:集群协调。
Broker 注册,靠它。 Controller 选举,靠它。 元数据存储,靠它。 Broker 上下线感知,也靠它。
但它也带来了麻烦。
Kafka 本身要部署一套,ZooKeeper 又要部署一套。Kafka 没问题,ZooKeeper 磁盘慢、网络抖、GC 卡一下,Kafka 控制面照样跟着晃。生产环境里最怕这种:数据面还在跑,控制面开始抽风,报错一会儿一个样。
现在新集群已经不建议再上 ZooKeeper 模式。Kafka 3.5 开始 ZooKeeper 被标记为 deprecated;Kafka 官方文档里也写了,ZooKeeper 在 3.x 还支持元数据管理,但不推荐新部署继续用,Kafka 4.0 之后走 KRaft 模式,控制面由 Kafka 自己的 controller quorum 承担。
所以回答“Kafka 中 ZooKeeper 的作用”,别只背一句“保存元数据”。
更准一点是:
ZooKeeper 是老版本 Kafka 的控制面底座。它不存业务消息,但它决定 Broker 怎么组成集群,谁来当 Controller,Topic 和分区元数据怎么维护,Broker 掉线后 Leader 怎么重新选。
消息丢不丢,主要看 Broker 副本、ISR、acks、刷盘这些配置。 集群乱不乱,老版本里 ZooKeeper 很关键。
现在要新搭 Kafka,我一般直接看 KRaft。 还在跑 ZooKeeper 模式的老集群,就别只监控 Kafka Broker。ZooKeeper 的延迟、连接数、磁盘、GC、session 过期日志,都得一起盯。控制面一抖,业务侧看到的往往已经是第二现场了。