作者 | 移动云 kafka 团队李鸿鹏
背 景
本文记录了一个在海量日志检索 ELK 场景中,Filebeat 采集日志写入到 Kafka 环节,Kafka 磁盘读写异常引发的故障。其中 Kafka 集群内每个 Broker 节点都设置了两个 log 目录,每个目录单独挂载一块盘,当单块磁盘读写异常时,Filebeat 出现了写入 Kafka 持续失败的问题。对 Kafka 来说,早有 KIP-112: Handle disk failure for JBOD 解决类似的问题,但为什么还会出现写入失败呢,接下来我们将对问题原因进行深入剖析。
处理过程和现象
下面分别从客户端和服务端角度说明一下当时的现象。
客户端:消息写入失败,持续有如下报错
服务端:网卡入流量从 40% 飙升到几乎打满,并且该 Broker 节点出现了 offline 的 replica Broker 日志里有如下 IO 异常
根据以上现象判断是 Broker 节点 /kafka/data-1 目录挂载的磁盘故障引发的问题,因为每个 Broker 节点设置了两个 log 目录,每个目录挂载一块盘,所以其中一块盘故障,Broker 节点进程并不会退出,但并不确定为什么消息会写入失败。
猜测 1:Broker 是否对位于故障磁盘目录的分区做了正确处理?通过 Kafka-topics.sh --describe 发现,这些分区已经完成了 leader 切换和 isr 剔除,从服务端角度看一切正常。
猜测 2:是不是和这个节点网卡入流量打满有关?
以为 Broker 会去其它副本同步因磁盘故障丢失的数据,从而影响了业务写入,考虑到主题每个分区都有三副本,通过关闭 Broker 节点进程的方式,停掉副本间同步的流量,随后业务消息写入恢复正常。
虽然业务临时恢复了,但我们还有很多疑问没有得到解答,下面将对以下问题进行深入分析
Kafka 服务端在挂载多块盘的场景中,其中单块盘故障的处理流程是怎么样的?
磁盘故障节点的网卡入流量打满到底和副本同步流量有没有关系?
本次问题的根本原因是什么?
分析过程
在进行问题分析前,有必要先了解一下问题发生的场景,因为问题一般只在某种特定的场景下发生,所以这部分非常重要。
问题发生场景
各组件版本信息如下表所示:
另外还需要注意的是,Kafka 集群的每个 Broker 节点配置两个 log 目录,每个目录挂载一块单独的磁盘,本次是某个 Broker 节点的其中一个目录挂载的磁盘发生了读写异常。
问题分析
Kafka 服务端在挂载多块盘的场景中,其中单块盘故障的处理流程是怎么样的?
Broker 运行时检测到 log 目录读写异常
分布在读写异常 log 目录下的副本执行和收到 StopReplicaRequest 一样的操作,充当 leader 角色的副本不再被视为 leader,充当 follower 角色的副本停止从其 leader 副本同步消息,Broker 同时会把自己内存里的副本状态标记成 Offline。
Broker 通知 Controller 节点自己发生了 LogDirFailure 事件
Controller 收到通知,发送 LeaderAndIsrRequest 请求查询这个 Broker 节点所有副本的状态,位于读写异常 log 目录下的副本将返回特定 KAFKA_STORAGE_ERROR 状态码
Controller 把返回 KAFKA_STORAGE_ERROR 状态码的副本标记成离线,触发分区 leader 重新选举
Controller 把离线的副本从 isr 里移除,并且给相关的 Broker 发送 LeaderAndIsrRequest 请求,告诉他们 isr 变化
Controller 在集群内广播最新的 metadata 信息
总结:通过以上分析可知,Kafka 服务端能及时将分布在故障磁盘目录的分区 leader 切换到其它副本,保证高可用。
磁盘故障节点的网卡入流量打满到底和副本同步流量有没有关系?
没关系,通过以上对 Kafka 故障转移实现原理的分析,磁盘故障节点并不会从其它节点同步消息,而且通过观察其它 Broker 节点的网卡出流量并没有明显变化也印证了这一点,说明我们当时猜测是错误的。
本次问题的根本原因是什么
通过问题一的分析已经排除了 Kafka 服务端的问题,接下来从客户端角度进行分析。首先我们对磁盘故障场景进行了复现,故障触发后 Filebeat 持续有如下报错直到 Broker 磁盘恢复,这个信息非常关键,说明 Filebeat 还是持续往磁盘故障的节点写入消息,这也正是 Broker 节点网卡入流量飙高的原因,针对这些写入请求,Broker 返回了 KafkaStorageException 的错误码。
怀疑是 Filebeat 所使用的 Kafka go sarama 客户端没有感知到分区 leader 的切换导致的,在从 go sarama 客户端角度分析为什么没有感知到之前,我们需要先看看 Broker 单块磁盘故障后,在服务端已经完成分区 leader 切换的情况下,客户端写入请求它是如何处理的,会给客户端返回什么样的错误,为什么是 KafkaStorageException 而不是 NotLeaderForPartitionException。
通过分析源码发现,Broker 处理生产请求时会先检查分区副本的状态,如果是 offline 状态的话就直接抛 KafkaStorageException 异常了,所以不会返回 NotLeaderForPartitionException。
现在重新回到 Kafka go sarama 客户端角度,sarama 在以下两种场景会从 Broker 执行拉取 metadata 更新分区 leader 的操作。
和 Broker 连接关闭
消息写入收到 ErrNotLeaderForPartition、ErrUnknownTopicOrPartition、ErrKafkaStorageException 等错误码,不过 ErrKafkaStorageException 是 2024/8/8 才加入进去的,在 sarama1.43.3 之前的版本,是不包含对这个错误码的处理的。
所以怀疑的重点就是使用的 sarama 版本不包含对 ErrKafkaStorageException 的处理,为了验证这个猜想,我们对 sarama 1.43.3 及之前的版本分别进行了验证。
sarama 1.43.3 表现符合预期,正常的感知到了分区 leader 的变化。
低于 1.43.3 之前,选取 Filebeat 最新版本 8.16.1 使用的 sarama1.27.0 版本,当磁盘故障后,它的分区的 leader 没有更新,发送消息会持续报错。
另外,和 Kafka 原生的 java 客户端对比,KafkaStorageException 是 InvalidMetadataException 的子类,收到这个错误码会更新 metadata,另外 Kafka java 的客户端还有定期更新 metadata 的机制,在 sarama 没有看到类似的实现,可见 sarama 的更新 metadata 机制并不完善。
总结:在 Broker 节点设置多个 log 目录,每个目录挂载一块单独的磁盘的场景,若其中一块盘发生故障,低于 1.43.3 版本的 sarama go 客户端没有对 Broker 返回的 ErrKafkaStorageException 错误码进行正确处理,导致一直感知不到分区 leader 的变化,所以消息写入持续失败。
解决方法
临时解决方法:重启 Filebeat 或重启磁盘异常的 Broker 节点,触发客户端拉取 metadata 感知分区 leader 变化。
长期解决方法:目前 Filebeat 最新版本 8.16.1 使用的 sarama 版本仍没有解决这个问题,只能通过单独升级 sarama 到 1.43.3 版本或等 Filebeat 更新 sarama 版本解决。
结 论
Kafka Broker 的确对上述磁盘故障场景做了很好的支持,问题原因是 Filebeat 所使用的 Kafka go sarama 客户端更新 metadata 的机制不完善,没有和 Kafka 原生 java 客户端对齐。Kafka Broker 可以及时的感知异常并进行分区 leader 的切换,但 Kafka go sarama 客户端无法感知到,仍旧往分区原来的分区 leader 发送消息,毫无疑问会发送失败,也就导致了本次问题。
4000520066 欢迎批评指正
All Rights Reserved 新浪公司 版权所有