【问题标题】:Aug 2019 - Kafka Consumer Lag programmatically2019 年 8 月 - Kafka Consumer Lag 以编程方式
【发布时间】:2019-12-09 15:37:32
【问题描述】:

有什么方法可以通过编程方式找到 Kafka Consumer 的延迟。 我不想在仪表板上安装和检查外部 Kafka 管理器工具。

我们可以列出所有消费者组并检查每个组的延迟。

目前我们确实有检查延迟的命令,它需要 Kafka 所在的相对路径。

Spring-Kafka、kafka-python、Kafka Admin 客户端或使用 JMX - 有什么方法可以编码并找出延迟。

我们粗心,没有监控过程,消费者处于僵尸状态,延迟达到50,000,导致混乱。

只有当问题出现时,我们才会想到这些情况,因为我们正在监视脚本但不知道它会导致僵尸进程。

非常欢迎任何想法!

【问题讨论】:

    标签: apache-kafka spring-kafka kafka-python


    【解决方案1】:

    如果有人正在寻找融合云中的消费者延迟,这里是简单的脚本

    BOOTSTRAP_SERVERS = "<>.aws.confluent.cloud"
    CCLOUD_API_KEY = "{{ ccloud_apikey }}"
    CCLOUD_API_SECRET = "{{ ccloud_apisecret }}"
    ENVIRONMENT = "dev"
    CLUSTERID = "dev"
    CACERT = "/usr/local/lib/python{{ python3_version }}/site-packages/certifi/cacert.pem"
    
    def main():
    
      client = KafkaAdminClient(bootstrap_servers=BOOTSTRAP_SERVERS,
                                ssl_cafile=CACERT,
                                security_protocol='SASL_SSL',
                                sasl_mechanism='PLAIN',
                                sasl_plain_username=CCLOUD_API_KEY,
                                sasl_plain_password=CCLOUD_API_SECRET)
    
      for group in client.list_consumer_groups():
        if group[1] == 'consumer':
          consumer = KafkaConsumer(
                                       bootstrap_servers=BOOTSTRAP_SERVERS,
                                       ssl_cafile=CACERT,
                                       group_id=group[0],
                                       enable_auto_commit=False,
                                       api_version=(0,10),
                                       security_protocol='SASL_SSL',
                                       sasl_mechanism='PLAIN',
                                       sasl_plain_username=CCLOUD_API_KEY,
                                       sasl_plain_password=CCLOUD_API_SECRET
                                     )
          list_members_in_groups =  client.list_consumer_group_offsets(group[0])
          for (topic,partition) in list_members_in_groups:
    
            
            consumer.topics()
    
            tp = TopicPartition(topic, partition)
            consumer.assign([tp])
            committed = consumer.committed(tp)
            consumer.seek_to_end(tp)
            last_offset = consumer.position(tp)
            if last_offset != None and committed != None:
              lag = last_offset - committed
              print("group: {} topic:{} partition: {} lag: {}".format(group[0], topic, partition, lag))
          consumer.close(autocommit=False)
    

    【讨论】:

      【解决方案2】:

      您可以使用 kafka-python 获取此信息,在每个代理上运行它或遍历代理列表,它会给所有主题分区消费者延迟。

      BOOTSTRAP_SERVERS = '{}'.format(socket.gethostbyname(socket.gethostname()))
      client = BrokerConnection(BOOTSTRAP_SERVERS, 9092, socket.AF_INET)
      client.connect_blocking()
      list_groups_request = ListGroupsRequest_v1()
      future = client.send(list_groups_request)
      while not future.is_done:
          for resp, f in client.recv():
            f.success(resp)
      for group in future.value.groups:
          if group[1] == 'consumer':
            #print(group[0])
            list_mebers_in_groups = DescribeGroupsRequest_v1(groups=[(group[0])])
            future = client.send(list_mebers_in_groups)
            while not future.is_done:
              for resp, f in client.recv():
                #print resp
                f.success(resp)
                (error_code, group_id, state, protocol_type, protocol, members) = future.value.groups[0]
                if len(members) !=0:
                  for member in members:
                    (member_id, client_id, client_host, member_metadata, member_assignment) = member
                    member_topics_assignment = []
                    for (topic, partitions) in MemberAssignment.decode(member_assignment).assignment:
                      member_topics_assignment.append(topic)
      
                    for topic in member_topics_assignment:
                      consumer = KafkaConsumer(
                                bootstrap_servers=BOOTSTRAP_SERVERS,
                                group_id=group[0],
                                enable_auto_commit=False
                                )
                      consumer.topics()
      
                      for p in consumer.partitions_for_topic(topic):
                        tp = TopicPartition(topic, p)
                        consumer.assign([tp])
                        committed = consumer.committed(tp)
                        consumer.seek_to_end(tp)
                        last_offset = consumer.position(tp)
                        if last_offset != None and committed != None:
                          lag = last_offset - committed
                          print "group: {} topic:{} partition: {} lag: {}".format(group[0], topic, p, lag)
      
                      consumer.close(autocommit=False)
      

      【讨论】:

        【解决方案3】:

        我在 scala 中编写代码,但仅使用来自 KafkaConsumerKafkaProducer 的本机 Java API。

        您只需要知道消费者组和主题的名称。 可以避免预定义的主题,但是只有存在且状态为stable 而不是重新平衡的消费者组才会得到滞后,这可能是警报的问题。 因此,您真正需要了解和使用的只是:

        1. KafkaConsumer.commited - 返回TopicPartition 的最新提交偏移量
        2. KafkaConsumer.assign - 不要使用订阅,因为它会导致 CG 重新平衡。您绝对不希望您的监控流程影响监控主题。
        3. kafkaConsumer.endOffsets - 返回最新产生的偏移量
        4. Consumer Group Lag - 是最新承诺和最新生产的区别
        import java.util.{Properties, UUID}
        
        import org.apache.kafka.clients.consumer.KafkaConsumer
        import org.apache.kafka.clients.producer.KafkaProducer
        import org.apache.kafka.common.TopicPartition
        import org.apache.kafka.common.serialization.{StringDeserializer, StringSerializer}
        
        import scala.collection.JavaConverters._
        import scala.util.Try
        
        case class TopicPartitionInfo(topic: String, partition: Long, currentPosition: Long, endOffset: Long) {
          val lag: Long = endOffset - currentPosition
        
          override def toString: String = s"topic=$topic,partition=$partition,currentPosition=$currentPosition,endOffset=$endOffset,lag=$lag"
        }
        
        case class ConsumerGroupInfo(consumerGroup: String, topicPartitionInfo: List[TopicPartitionInfo]) {
          override def toString: String = s"ConsumerGroup=$consumerGroup:\n${topicPartitionInfo.mkString("\n")}"
        }
        
        object ConsumerLag {
        
          def consumerGroupInfo(bootStrapServers: String, consumerGroup: String, topics: List[String]) = {
            val properties = new Properties()
            properties.put("bootstrap.servers", bootStrapServers)
            properties.put("auto.offset.reset", "latest")
            properties.put("group.id", consumerGroup)
            properties.put("key.deserializer", classOf[StringDeserializer])
            properties.put("value.deserializer", classOf[StringDeserializer])
            properties.put("key.serializer", classOf[StringSerializer])
            properties.put("value.serializer", classOf[StringSerializer])
            properties.put("client.id", UUID.randomUUID().toString)
        
            val kafkaProducer = new KafkaProducer[String, String](properties)
            val kafkaConsumer = new KafkaConsumer[String, String](properties)
            val assignment = topics
              .map(topic => kafkaProducer.partitionsFor(topic).asScala)
              .flatMap(partitions => partitions.map(p => new TopicPartition(p.topic, p.partition)))
              .asJava
            kafkaConsumer.assign(assignment)
        
            ConsumerGroupInfo(consumerGroup,
              kafkaConsumer.endOffsets(assignment).asScala
                .map { case (tp, latestOffset) =>
                  TopicPartitionInfo(tp.topic,
                    tp.partition,
                    Try(kafkaConsumer.committed(tp)).map(_.offset).getOrElse(0), // TODO Warn if Null, Null mean Consumer Group not exist
                    latestOffset)
                }
                .toList
            )
        
          }
        
          def main(args: Array[String]): Unit = {
            println(
              consumerGroupInfo(
                bootStrapServers = "kafka-prod:9092",
                consumerGroup = "not-exist",
                topics = List("events", "anotherevents")
              )
            )
        
            println(
              consumerGroupInfo(
                bootStrapServers = "kafka:9092",
                consumerGroup = "consumerGroup1",
                topics = List("events", "anotehr events")
              )
            )
          }
        }
        
        

        【讨论】:

        • 嗨-我写了一个python代码,它将执行Kafka命令(来自Kafka目录)并获取消费者组,然后下一段代码是遍历每个消费者组并执行描述命令对于使用 python 的每个组并获取 Lag 列并将其打印到主题。 [DRAWBACK-Code 需要原生到 Kafka Directory 并且需要执行 shell 命令来获取详细信息!]
        • @ArpanSharma kafka-consumer-group.sh 有问题。此命令仅返回 stable 消费者组的结果。如果rebalance 中的消费者组或所有实例都关闭,该命令将不返回任何内容。
        【解决方案4】:

        Java 客户端通过 JMX 向其消费者公开延迟;在这个例子中,我们有 5 个分区...

        Spring Boot 可以将这些发布到千分尺。

        【讨论】:

        • 嗨,加里如果你能在你的卡夫卡春季讲座/视频中展示这个会很棒!!
        • 这实际上与 Spring 无关; MBean 由kafka-clients 导出。 Spring Boot 只是具有读取这些 MBean 并将它们发布到 micrometer 的钩子。
        • 但是 python 消费者发生了什么然后我的 jconsole 没有显示 kafka.consumer,只有在我的 Spring kafka 消费者显示的情况下......我错过了什么吗?
        • 否; java 客户端只会为自己的消费者导出 MBean。
        • 那么我们可以为那些消费者做些什么,我已经看到你在Java程序中调用消费者组描述命令并加载滞后值的答案......但是我们没有其他方法可以实现它???
        【解决方案5】:

        是的。我们可以在 kafka-python 中获得消费者滞后。不确定这是否是最好的方法。但这行得通。

        目前我们手动给消费者,你也从 kafka-python 获取消费者,但它只给出活跃消费者的列表。因此,如果您的一位消费者失败了。它可能不会出现在列表中。

        首先建立客户端连接

        from kafka import BrokerConnection
        from kafka.protocol.commit import *
        import socket
        
        #This takes in only one broker at a time. So to use multiple brokers loop through each one by giving broker ip and port.
        
        def establish_broker_connection(server, port, group):
            '''
            Client Connection to each broker for getting consumer offset info
            '''
            bc = BrokerConnection(server, port, socket.AF_INET)
            bc.connect_blocking()
            fetch_offset_request = OffsetFetchRequest_v3(group, None)
            future = bc.send(fetch_offset_request)
        

        接下来我们需要获取消费者订阅的每个主题的当前偏移量。把上面的future和bc传到这里。

        from kafka import SimpleClient
        from kafka.protocol.offset import OffsetRequest, OffsetResetStrategy
        from kafka.common import OffsetRequestPayload
        
        def _get_client_connection():
            '''
            Client Connection to the cluster for getting topic info
            '''
            # Give comma seperated info of kafka broker "broker1:port1, broker2:port2'
            client = SimpleClient(BOOTSTRAP_SEREVRS)
            return client
        
        def get_latest_offset_for_topic(self, topic):
            '''
            To get latest offset for a topic
            '''
            partitions = self.client.topic_partitions[topic]
            offset_requests = [OffsetRequestPayload(topic, p, -1, 1) for p in partitions.keys()]
            client = _get_client_connection()
            offsets_responses = client.send_offset_request(offset_requests)
            latest_offset = offsets_responses[0].offsets[0]
            return latest_offset # Gives latest offset for topic
        
        def get_current_offset_for_consumer_group(future, bc):
            '''
            Get current offset info for a consumer group
            '''
            while not future.is_done:
                for resp, f in bc.recv():
                    f.success(resp)
        
            # future.value.topics -- This will give all the topics in the form of a list.
            for topic in self.future.value.topics:
                latest_offset = self.get_latest_offset_for_topic(topic[0])
                for partition in topic[1]:
                    offset_difference = latest_offset - partition[1]
        

        offset_difference 给出了主题中产生的最后一个偏移量与消费者消费的最后一个偏移量(或消息)之间的差异。

        如果您没有为某个主题的消费者获取当前偏移量,那么这意味着您的消费者可能已关闭。

        因此,如果偏移量差异高于您想要的阈值,或者您的消费者获得空偏移量,您可以发出警报或发送邮件。

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多