当前位置: 首页 > 科技观察

生产故障|Kafka消息发送延迟达到几十秒的罪魁祸首是...

时间:2023-03-20 19:07:03 科技观察

之前就知道,不知道为什么。为什么Kafka在2.8版本中“废弃”了Zookeeper官方给出了AbandonedZookeeper的原因解释。当时记得有读者反驳说zookeeper很稳定,基本不会出问题。双十一遇到的问题证明了Zookeeper的“脆弱性”,而Zookeeper的脆弱性会影响到Kafka。集群的后果很严重。1、故障现象双十一期间笔者负责的Kafka集群响应时间飙升至10-30s,严重影响了消息的写入。通过日志分析,发现存在大面积的partitionleader选举,__consumer_offsets主题的partition也进行partitionleader选举,导致消息发送几乎停顿,大量consumergroup触发rebalancing,整个集群接近瘫痪。最终确定了根本原因:Broker节点与Zookeeper的会话超时,引发了大量的分区重选。本文借此故障与大家一起分析一下Zookeeper在Kafka中扮演着怎样的角色,以及确定“罪魁祸首”的过程,希望能给大家排查问题带来一些启发。2、Zookeeper在Kafka中起着举足轻重的作用。在正式进入故障分析之前,我们先介绍一下Zookeeper在Kafka架构设计中的作用。核心理念:Kafka的设计者对Zookeeper的使用非常谨慎,即需要依赖Zookeeper进行controller选举,实时检测Broker节点故障,但尽量减少对Zookeeper的依赖。对于基于Zookeeper的程序开发,我们一般可以查看Zookeeper中的目录布局,看看Zookeeper完成了哪些功能。Kafka在Zookeeper中的存储目录结构如下图所示:以上每一个节点都关联着Kafka是一个核心的工作机制,大家可以顺藤摸瓜去探索。本文需要重点介绍/brokers目录的布局和功能。目录详细信息如下:/controllerKafkacontroller的信息,Kafkacontroller的选举依赖于zookeeper。/brokers/ids/{id}在持久化节点/brokers/ids下创建很多临时节点,每个节点代表一个Broker节点,节点内容存放Broker的基本信息,如端口、版本、监听地址等/brokers/topics/{topic}/partitions/{partition}/state在kafka2.8以下,Kafka中topic中的路由信息??最终持久化到zookeeper中,每个broker节点启动后都会在内存中缓存一份数据。/brokers节点的每个子节点代表一个特定的主题,主题的元数据主要包括分区数量和每个分区的状态信息。每个分区的状态信息主要包括:controller_epoch当前集群controller的epoch,表示controller选举的次数,我们可以理解为controller的“版本号”。leader当前分区的leader所在的brokerid。Leader_epoch分区的leader_epoch表示分区领导选举的次数。从0开始,每发生一次partitionleader选举,该值就会加1。Kafka引入leaderepoch机制,解决低版本依赖水位指示副本进度可能导致的数据丢失。关于数据不一致的问题,后续文章会深入分析。isrisr分区的集合。version存储状态分区状态数据结构的版本号。您可以忽略此字段。Zookeeper中也有相同的“设计模式”,即可以在Zookeeper中创建临时节点+事件监听机制,实现对数据的实时动态感知。以/brokers/ids为例:Kafkabroker进程启动时,会创建一个临时节点/brokers/ids/{id}给zookeeper,其中id为broker的编号。KafkaBroker进程停止后,创建的临时节点在broker和zookeepersession超时后自动删除,并产生节点删除事件。Kafka控制器会自动监听/brokers/ids目录下的节点增删事件。一旦broker下线或上线,controller会实时感知并进行必要的处理。经过上面的初步介绍,Kafka对zookeeper的依赖还是很大的,尤其是Kafkacontroller的选举,broker节点的生存状态等都依赖于zookeeper。可见Kafka控制器是整个Kafka集群的“大脑”。一旦发生变化,影响的范围和程度可想而知。下面的故障分析会给出更直观的展示。温馨提示:本文主要是故障分析过程。后续关于kafkacontroller如何选举以及leader_epoch副本同步机制等内容会在《Kafka原理与实战》专栏中一一介绍,敬请期待。3、问题分析看到消息发送响应时间长,第一反应是查看线程栈,看是否有锁阻塞,但是发现大部分是Kafka处理请求使用的线程池在获取任务时被阻止,表示“无事可做”的状态:表示客户端的消息发送请求都没有到达Kafka的queuing队列,专门处理网络读写的线程池也很空闲,这是为什么呢?消息发送延迟超高,但是服务器线程却极度空闲,有点奇怪?继续查看服务器日志,发现很多主题(连系统主题__consumer_offsets主题也有leader选举),日志如下:Corelog:startatLeaderEpochalotthepartitionisintheprocessofleaderelection.在Kafka中,只有leaderpartition可以处理读写请求。follower分区只是从leader分区复制数据,在leader节点宕机后参与leader选举。因此分区在leader选举时无法处理client的写请求,而sender也有重试机制,所以消息发送延迟非常大。那么在什么情况下会出现大量的选题重选呢?我们找到当前集群的Controler节点,查看state-change.log,发现如下日志:大量分区的状态由OnlinePartition变为OfflinePartition。温馨提示:我们可以根据日志查看源码,找到输出这些方法的调用链,然后顺藤摸瓜找到目标日志。继续查看Controller节点下的controller.log,找到关键日志:核心日志解读:[Controllerid=1]Brokerfailurecallbackfor8(kafka.controller.KafkaController)controller从线上集群中移除节点8,并控件为什么服务器删除节点8?接下来顺藤摸瓜,查看节点8上的日志,如下图:核心日志解读:原来是broker和zookeeper的会话超时,导致临时节点被移除。先不探究session为什么会超时。我们来看看session超时会对kafka集群造成什么严重影响。如果/brokers/ids下的任何节点被删除,Kafkacontroller可以及时获取并进行相应的处理。这里需要考虑两种情况。3.1CommonBroker节点移除处理入口为:KafkaController的onBrokerFailure方法,代码详情如下图:zk中移除一个commonbroker,Kafkacontroller会将节点上分配的所有partition的状态从OnBrokerFailure改为OfflinePartition触发重新选择分区。Extendedknowledgepoint:Ifthe__consumer_offsetspartitionisre-electedasaleader,alarge-scaleconsumergroupwilltr??iggerrebalancing.背后的机制:consumergroup需要在Broker端进行groupcoordinator选举。选举算法如下:consumergroup名称和topic的hashcode对__consumer_offsets中的队列总数取模,取余,映射到__consumer_offsets分区,哪个broker节点是分区的leader,节点将充当消费者组的组协调器。一旦partition的leader发生变化,对应的consumergroup就必须重新选举一个新的groupcoordinator,从而触发consumergroup的rebalancing。3.2controller节点被移除如果zookeeper中被移除的brokerid是kafkacontroller,影响会更大。主入口如下图所示:如果controller节点的session超时,临时节点/controller节点会被删除,从而触发Kafkacontroller选举,最终所有broker节点都会收到节点/controller删除的通知、新增或节点数据变更,KafkaController的onControllerFailover方法会被执行,重新注册zookeeper相关的事件监听器,分区状态机和副本状态机停止重启,每个分区都会触发leader分区自动选举。可以这样形容:一朝君臣,皆从头再来。3.3解决zookeeper会话超时的根本原因查看服务器日志,可以看到如下日志:核心日志解读:Closedsocketconnectionforclient...表示连接被客户端主动关闭。那为什么客户端会主动关闭心跳呢?心跳处理的套路是客户端需要定时向服务器发送一个心跳包。如果服务器在指定时间内没有收到或处理心跳包,就会超时。要想一探究竟,唯一的办法就是阅读源码,通过研究Zookeeper客户端的源码,你会发现有这样的设计:客户端会先将所有的请求放入一个队列中,然后通过发送线程(SendThread)从队列中发送它们。关键代码如下:如果有大量的zk更新操作,心跳包可能没有及时处理,在zookeeper会话超时之前,集群在ISR大面积扩缩容,频繁更新zk,从而触发客户端心跳超时。这个问题也可以通过下面的代码复现:经过这一波分析,由于zookeeper会话超时,导致大量partition重新选举,最终导致发送消息延迟大,大量consumergroup的area重平衡的根本原因已经查清楚了,所以本次分享就到此为止,我们下期再见。