你说通过Kafka AdminClient获取Lag会有性能问题?尊嘟假嘟0.o

0.前言

前阵子团队里出了个大故障,本质是因为其他语言实现的client有问题,非常频繁的请求大量元数据,而Kafka服务端这边也没有做什么限制,导致Kafka Broker宕了。

在相关的复盘报告中,复盘方提到了我这边的监控程序(用于观察线上实时作业的堆压)会频繁的去获取一些元数据,也是在间接的增加Kafka集群的压力,建议修改成消费__consumer_offsets的方式。(我这边用的是AdminClient#listConsumerGroupOffsets和AdminClient.listOffsets来获取commit和end的offset)

这个事老哥之前有和我沟通过几次,那时我问他:你这边有什么根据吗?他没有正面回答我——听说这老哥之前在别的地方维护过很大的Kafka集群,对此我半信半疑的在网上搜索过一阵子,但是并没有找到对应的答案。

直到这次,我这边的监控程序被要求整改。对此我觉得莫名其妙,于是有了这篇文章——我们来扒一扒源码。

本文的代码基于Kafka 3.9。

消费__consumer_offsets本质上来说就是Consumer顺序读Broker上的日志,消费过程这块网上源码解析非常多,总体来说代价也不大,就不再赘述了。我们直接来看AdminClient上的实现。

1.AdminClient相关源码分析

1.1 AdminClient#listConsumerGroupOffsets

这边整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

 需要全套面试笔记的【点击此处即可】免费获取

bash

代码解读

复制代码

|--ListConsumerGroupOffsetsHandler |--ApiKeys.OFFSET_FETCH \--handleOffsetFetchRequest \--handleOffsetFetchRequestFromCoordinator \--handleOffsetFetchRequestFromZookeeper

在早期版本中,kafka的元数据是保存在的zk里的。为了更全面的带大家阅读代码,我们把两个实现都读一遍。

From KRaft

 

bash

代码解读

复制代码

\--fetchOffsetsForGroup |--GroupCoordinatorAdapter \-- fetchOffsets \--handleFetchOffset |--GroupCoordinator \--handleFetchOffsets |--GroupmetadataManager \--getOffsets

那么从getOffsets的实现为:

 

vbnet

代码解读

复制代码

def getOffsets(groupId: String, requireStable: Boolean, topicPartitionsOpt: Option[Seq[TopicPartition]]): Map[TopicPartition, PartitionData] = { trace("Getting offsets of %s for group %s.".format(topicPartitionsOpt.getOrElse("all partitions"), groupId)) val group = groupMetadataCache.get(groupId) if (group == null) { topicPartitionsOpt.getOrElse(Seq.empty[TopicPartition]).map { topicPartition => val partitionData = new PartitionData(OffsetFetchResponse.INVALID_OFFSET, Optional.empty(), "", Errors.NONE) topicPartition -> partitionData }.toMap } else { group.inLock { if (group.is(Dead)) { topicPartitionsOpt.getOrElse(Seq.empty[TopicPartition]).map { topicPartition => val partitionData = new PartitionData(OffsetFetchResponse.INVALID_OFFSET, Optional.empty(), "", Errors.NONE) topicPartition -> partitionData }.toMap } else { val topicPartitions = topicPartitionsOpt.getOrElse(group.allOffsets.keySet) topicPartitions.map { topicPartition => if (requireStable && group.hasPendingOffsetCommitsForTopicPartition(topicPartition)) { topicPartition -> new PartitionData(OffsetFetchResponse.INVALID_OFFSET, Optional.empty(), "", Errors.UNSTABLE_OFFSET_COMMIT) } else { val partitionData = group.offset(topicPartition) match { case None => new PartitionData(OffsetFetchResponse.INVALID_OFFSET, Optional.empty(), "", Errors.NONE) case Some(offsetAndMetadata) => new PartitionData(offsetAndMetadata.offset, offsetAndMetadata.leaderEpoch, offsetAndMetadata.metadata, Errors.NONE) } topicPartition -> partitionData } }.toMap } } } }

在这里我们可以看到,相关的信息其实从groupMetadataCache这个内存缓存中获取的, 并不是一个很重的操作。而缓存的load方法是loadGroupsAndOffsets,因为篇幅原因,不再展开,有兴趣的同学可以自行阅读。

From Zookeeper

逻辑非常简单,直接粘代码:

 

scss

代码解读

复制代码

private def handleOffsetFetchRequestFromZookeeper(request: RequestChannel.Request): CompletableFuture[Unit] = { val header = request.header val offsetFetchRequest = request.body[OffsetFetchRequest] def createResponse(requestThrottleMs: Int): AbstractResponse = { val offsetFetchResponse = // reject the request if not authorized to the group if (!authHelper.authorize(request.context, DESCRIBE, GROUP, offsetFetchRequest.groupId)) offsetFetchRequest.getErrorResponse(requestThrottleMs, Errors.GROUP_AUTHORIZATION_FAILED) else { val zkSupport = metadataSupport.requireZkOrThrow(KafkaApis.unsupported("Version 0 offset fetch requests")) val (authorizedPartitions, unauthorizedPartitions) = partitionByAuthorized( offsetFetchRequest.partitions.asScala, request.context) // version 0 reads offsets from ZK val authorizedPartitionData = authorizedPartitions.map { topicPartition => try { if (!metadataCache.contains(topicPartition)) (topicPartition, OffsetFetchResponse.UNKNOWN_PARTITION) else { val payloadOpt = zkSupport.zkClient.getConsumerOffset(offsetFetchRequest.groupId, topicPartition) payloadOpt match { case Some(payload) => (topicPartition, new OffsetFetchResponse.PartitionData(payload, Optional.empty(), OffsetFetchResponse.NO_METADATA, Errors.NONE)) case None => (topicPartition, OffsetFetchResponse.UNKNOWN_PARTITION) } } } catch { case e: Throwable => (topicPartition, new OffsetFetchResponse.PartitionData(OffsetFetchResponse.INVALID_OFFSET, Optional.empty(), OffsetFetchResponse.NO_METADATA, Errors.forException(e))) } }.toMap val unauthorizedPartitionData = unauthorizedPartitions.map(_ -> OffsetFetchResponse.UNAUTHORIZED_PARTITION).toMap new OffsetFetchResponse(requestThrottleMs, Errors.NONE, (authorizedPartitionData ++ unauthorizedPartitionData).asJava) } trace(s"Sending offset fetch response $offsetFetchResponse for correlation id ${header.correlationId} to client ${header.clientId}.") offsetFetchResponse } requestHelper.sendResponseMaybeThrottle(request, createResponse) CompletableFuture.completedFuture[Unit](()) }

首先检查用户是否被授权访问指定的组,如果没有授权,则返回授权失败的错误响应。然后根据请求中的分区信息,将分区分为授权和未授权的分区。对于授权的分区,尝试从ZooKeeper中获取消费偏移量,并根据结果生成相应的分区数据。如果出现异常,则生成一个包含无效偏移量的分区数据。最后将授权和未授权的分区数据合并,并将响应发送回客户端。

1.2 AdminClient#listOffsets

 

bash

代码解读

复制代码

|--ListOffsetsHandler |--ApiKeys.LIST_OFFSETS \--handleListOffsetRequest \--handleListOffsetRequestV0 \--handleListOffsetRequestV1AndAbove

这里也分成了两个版本,引入KRaft之前是handleListOffsetRequestV0,之后则是handleListOffsetRequestV1AndAbove。除了在部分功能支持的差异和错误处理更加细致外,核心调用的replicaManager.fetchOffsetForTimestamp并无变化。而这个函数的底层实现本质是调用Kafka Log,即去Broker的Log上查询相关的信息。

2.小结

listConsumerGroupOffsets这个命令在KRaft之前的实现是读取Zookeeper,但由于ZK存储的特性,小量点查的代价并不大。如果在启用KRaft的情况下,并不是什么性能瓶颈。

listOffsets则是通过Kafka Broker读取对应Topic Partition中的Log实现的,相比Consumer消费__consumer_offsets来说,性能在其之下——如果进行大频次的读,本质上来说是在做随机IO读,是比不上消费__consumer_offsets的顺序读的。如果高频次的做读取操作,是一定会引起IO压力的。

  • 8
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
KafkaAdminClient 是 Apache Kafka 提供的一个 Java API,用于管理 Kafka 集群。它是 Kafka 的一个重要组件,可以让我们通过编程的方式对 Kafka 集群进行管理,例如创建和删除主题、添加和删除分区、修改配置等。 KafkaAdminClient 提供了一些常用的管理操作方法,可以通过它来管理 Kafka 集群的元数据。我们可以使用 KafkaAdminClient 来通过指定的配置创建一个 KafkaAdminClient 对象,在构建 KafkaAdminClient 对象时,我们需要指定一些和 Kafka 集群连接相关的配置,例如 Kafka 集群的地址、端口等。创建了 KafkaAdminClient 对象之后,我们就可以使用它提供的方法来执行我们所需的管理操作。 KafkaAdminClient 提供了一些主要的方法,例如 createTopics()、deleteTopics()、addPartitions()、deleteRecords() 等。通过这些方法,我们可以进行创建主题、删除主题、修改分区、删除消息等操作。同时,KafkaAdminClient 还提供了一些其他辅助方法,用于获取集群的元数据信息、获取主题的配置等。 KafkaAdminClient 的设计使得管理 Kafka 集群变得十分便捷和灵活,同时也提供了一些保护机制,例如对于一些敏感的操作,需要进行权限校验。此外,KafkaAdminClient 还与 KafkaConsumer 和 KafkaProducer 等其他 Kafka 客户端紧密集成,可以在使用 KafkaAdminClient 的同时,进行消息的消费和生产。 总的来KafkaAdminClient 是一个功能强大的管理 Kafka 集群的 Java API,可以让我们通过编程的方式进行细粒度的管理操作,帮助我们更好地管理 Kafka 集群。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值