【问题标题】:How to get kafka consume lag in java program如何在java程序中获取kafka消耗滞后
【发布时间】:2017-07-01 07:04:09
【问题描述】:

我写了一个java程序来消费来自kafka的消息。我想监控消费延迟,如何通过java获取?

顺便说一句,我使用:

<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.11</artifactId>
<version>0.10.1.1</version>

提前致谢。

【问题讨论】:

  • 我会在消息写入时为其添加时间戳,并将其与读取时间进行比较以获得端到端的时间。
  • 其实我是想得到滞后数
  • 你会将延迟计数用于什么目的?
  • 我想检查我的消费者是否延迟
  • 在这种情况下,我建议您以毫秒为单位进行测量。包含一条消息的队列可能是 ms 或 mins。

标签: java apache-kafka


【解决方案1】:

如果您不想在项目中包含 kafka(和 scala)依赖项,您可以使用下面的类。它仅使用 kafka-clients 依赖项。

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.PartitionInfo;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;

public class KafkaConsumerMonitor {

    public static class PartionOffsets {
        private long endOffset;
        private long currentOffset;
        private int partion;
        private String topic;

        public PartionOffsets(long endOffset, long currentOffset, int partion, String topic) {
            this.endOffset = endOffset;
            this.currentOffset = currentOffset;
            this.partion = partion;
            this.topic = topic;
        }

        public long getEndOffset() {
            return endOffset;
        }

        public long getCurrentOffset() {
            return currentOffset;
        }

        public int getPartion() {
            return partion;
        }

        public String getTopic() {
            return topic;
        }
    }

    private final String monitoringConsumerGroupID = "monitoring_consumer_" + UUID.randomUUID().toString();

    public Map<TopicPartition, PartionOffsets> getConsumerGroupOffsets(String host, String topic, String groupId) {
        Map<TopicPartition, Long> logEndOffset = getLogEndOffset(topic, host);


        KafkaConsumer consumer = createNewConsumer(groupId, host);

        BinaryOperator<PartionOffsets> mergeFunction = (a, b) -> {
            throw new IllegalStateException();
        };

        Map<TopicPartition, PartionOffsets> result = logEndOffset.entrySet()
                .stream()
                .collect(Collectors.toMap(
                        entry -> (entry.getKey()),
                        entry -> {
                            OffsetAndMetadata committed = consumer.committed(entry.getKey());
                            return new PartionOffsets(entry.getValue(), committed.offset(), entry.getKey().partition(), topic);
                        }, mergeFunction));


        return result;
    }

    public Map<TopicPartition, Long> getLogEndOffset(String topic, String host) {
        Map<TopicPartition, Long> endOffsets = new ConcurrentHashMap<>();
        KafkaConsumer<?, ?> consumer = createNewConsumer(monitoringConsumerGroupID, host);
        List<PartitionInfo> partitionInfoList = consumer.partitionsFor(topic);
        List<TopicPartition> topicPartitions = partitionInfoList.stream().map(pi -> new TopicPartition(topic, pi.partition())).collect(Collectors.toList());
        consumer.assign(topicPartitions);
        consumer.seekToEnd(topicPartitions);
        topicPartitions.forEach(topicPartition -> endOffsets.put(topicPartition, consumer.position(topicPartition)));
        consumer.close();
        return endOffsets;
    }

    private static KafkaConsumer<?, ?> createNewConsumer(String groupId, String host) {
        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, host);
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        return new KafkaConsumer<>(properties);
    }
}

【讨论】:

  • OffsetAndMetadata 提交; for(TopicPartition topicPartition : topicPartitions){ log.info("topic partion is {}", topicPartition); log.info("consumer is {}", consumer.position(topicPartition)); committ = consumer.committed(topicPartition); log.info("commited is {}", committ.offset());我得到的偏移量为空,但所有其他值都在那里,你能把我推到正确的方向吗?
  • 这个偏移量可能已经过期。 Kafka 将分区数据保留一段时间(取决于主题保留策略。在我们的例子中是 7 天)。如果您的消费者从不使用主题中的数据,或者最近的消费在此保留期之后,您将获得空偏移量。在这种情况下,如何定义消费者滞后取决于您。它可能被定义为零(如果您的 Consumer auto.offset.reset 设置是最新的)或者您需要获取流中最早消息的偏移量并将偏移量计算为 endOffset-earliestOffset。
  • 我认为您不能使用分配的消费者来查看单独订阅消费者的提交偏移量。我认为这就是为什么您通过分配的消费者 consumer.committed(topicPartition) 调用获得空值的原因。您创建的指定使用者可能从未完成过提交
【解决方案2】:

我个人直接从我的消费者那里查询 jmx 信息。我只在 java 中消费,所以 JMX bean :kafka.consumer:type=consumer-fetch-manager-metrics,client-id=*/records-lag-max 可用。

如果 jolokia 在您的类路径中,您可以在 /jolokia/read/kafka.consumer:type=consumer-fetch-manager-metrics,client-id=*/records-lag-max 上使用 GET 检索该值并将所有结果收集到一个地方。

还有Burrow,很容易配置,但是有点过时了(如果我没记错的话,在0.10上不适用)。

【讨论】:

  • 我检查了 jmx,这个 JMX bean:kafka.server:type=FetcherLagMetrics,name=ConsumerLag,clientId=mygroup,topic=mytpoic,partition=* 包含滞后信息
  • 我在集群中运行 Kafka,你能告诉我如何启用和导出这个特定的 JMX bean 吗?
  • 这个 bean 是每个消费者客户端,而不是 Kafka 集群本身
【解决方案3】:

我将 Spring 用于我的 api。使用以下代码,您可以通过 java 获取指标。代码有效。

@Component
public class Receiver {

private static final Logger LOGGER =
      LoggerFactory.getLogger(Receiver.class);


@Autowired
private KafkaListenerEndpointRegistry kafkaListenerEndpointRegistry;

  public void testlag() {
      for (MessageListenerContainer messageListenerContainer : kafkaListenerEndpointRegistry
                .getListenerContainers()) {
          Map<String, Map<MetricName, ? extends Metric>> metrics = messageListenerContainer.metrics();
          metrics.forEach( (clientid, metricMap) ->{
              System.out.println("------------------------For client id : "+clientid);
              metricMap.forEach((metricName,metricValue)->{
                  //if(metricName.name().contains("lag"))
                  System.out.println("------------Metric name: "+metricName.name()+"-----------Metric value: "+metricValue.metricValue());
              });
          });
            }
  }

【讨论】:

  • 就我而言,我没有看到任何包含“滞后”的指标名称!找出消费者滞后的具体指标是什么?
  • @AryanVenkat 我在控制台上看到以下与滞后相关的指标:i)customers-2.records-lag ii)customers-2.records-lag-avg iii)customers-2.records-滞后最大值。这里customers是主题名称,'2'是分区。
【解决方案4】:

您可以在创建消费者时设置 SetStatisticsHandler 回调函数。 比如c#代码如下

var config = new ConsumerConfig()
    {
      BootstrapServers = entrypoints,
      GroupId = groupid,
      EnableAutoCommit = false,
      StatisticsIntervalMs=1000 // statistics interval time
    };

    var consumer = new ConsumerBuilder<Ignore, byte[]>( config )
    .SetStatisticsHandler((consumer,json)=> {
      logger.LogInformation( json ); // statistics metrics, include consumer lag
    } )
    .Build();

详情请参考STATISTICS.md中的统计指标。

【讨论】:

    【解决方案5】:

    尝试使用 AdminClient#listGroupOffsets(groupID) 来检索与消费者组关联的所有主题分区的偏移量。例如:

    AdminClient client = AdminClient.createSimplePlaintext("localhost:9092");
    Map<TopicPartition, Object> offsets = JavaConversions.asJavaMap(
        client.listGroupOffsets("groupID"));
    Long offset = (Long) offsets.get(new TopicPartition("topic", 0));
    ...
    

    编辑
    上面的片段显示了如何获取给定分区的提交偏移量。下面的代码显示了如何检索分区的 LEO。

    public long getLogEndOffset(TopicPartition tp) {
        KafkaConsumer consumer = createNewConsumer();
        Collections.singletonList(tp);
        consumer.assign(Collections.singletonList(tp));
        consumer.seekToEnd(Collections.singletonList(tp));
        return consumer.position(tp);
    }
    
    private KafkaConsumer<String, String> createNewConsumer() {
        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "g1");
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
        properties.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000");
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        return new KafkaConsumer(properties);
    }
    

    调用getLogEndOffset返回给定分区的LEO,然后从中减去提交的偏移量,结果就是滞后。

    【讨论】:

    • AdminClient 是从哪里来的?你能给我maven依赖吗?非常感谢。
    • 它来自您使用的kafka_2.11中已经存在的kafka.admin包。
    • 这给出了当前的偏移量。如何获得提交的偏移量?
    【解决方案6】:

    供您参考,我使用下面的代码完成了这项工作。基本上,您必须通过计算当前提交的偏移量和结束偏移量之间的增量来手动计算每个主题分区的滞后。

    private static Map<TopicPartition, Long> lagOf(String brokers, String groupId) {
        Properties props = new Properties();
        props.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, brokers);
        try (AdminClient client = AdminClient.create(props)) {
            ListConsumerGroupOffsetsResult currentOffsets = client.listConsumerGroupOffsets(groupId);
    
            try {
                // get current offsets of consuming topic-partitions
                Map<TopicPartition, OffsetAndMetadata> consumedOffsets = currentOffsets.partitionsToOffsetAndMetadata()
                        .get(3, TimeUnit.SECONDS);
                final Map<TopicPartition, Long> result = new HashMap<>();
                doWithKafkaConsumer(groupId, brokers, (c) -> {
                    // get latest offsets of consuming topic-partitions
                    // lag = latest_offset - current_offset
                    Map<TopicPartition, Long> endOffsets = c.endOffsets(consumedOffsets.keySet());
                    result.putAll(endOffsets.entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey(),
                            entry -> entry.getValue() - consumedOffsets.get(entry.getKey()).offset())));
                });
                return result;
            } catch (InterruptedException | ExecutionException | TimeoutException e) {
                log.error("", e);
                return Collections.emptyMap();
            }
        }
    }
    
    public static void doWithKafkaConsumer(String groupId, String brokers,
            Consumer<KafkaConsumer<String, String>> consumerRunner) {
        Properties props = new Properties();
        props.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, brokers);
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
        props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
    
        try (final KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
            consumerRunner.accept(consumer);
        }
    }
    

    请注意,一个消费者组可能同时消费多个主题,因此如果您需要获取每个主题的延迟,那么您必须按主题对结果进行分组和聚合。

        Map<TopicPartition, Long> lags = lagOf(brokers, group);
        Map<String, Long> topicLag = new HashMap<>();
        lags.forEach((tp, lag) -> {
            topicLag.compute(tp.topic(), (k, v) -> v == null ? lag : v + lag);
        });
    

    【讨论】:

    • 能否请您包括完整的课程,这将非常有帮助
    【解决方案7】:

    运行此独立代码。 (依赖kafka-clients-2.6.0.jar)

    import java.util.HashSet;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Properties;
    import java.util.Set;
    import java.util.UUID;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.function.BinaryOperator;
    import java.util.stream.Collectors;
    
    import org.apache.kafka.clients.consumer.ConsumerConfig;
    import org.apache.kafka.clients.consumer.KafkaConsumer;
    import org.apache.kafka.clients.consumer.OffsetAndMetadata;
    import org.apache.kafka.common.PartitionInfo;
    import org.apache.kafka.common.TopicPartition;
    import org.apache.kafka.common.serialization.StringDeserializer;
    
    public class CosumerGroupLag {
    
    static String host = "localhost:9092";
    static String topic = "topic02";
    static String groupId = "test-group";
    
    public static void main(String... vj) {
        CosumerGroupLag cgl = new CosumerGroupLag();
    
        while (true) {
            Map<TopicPartition, PartionOffsets> lag = cgl.getConsumerGroupOffsets(host, topic, groupId);
            System.out.println("$$LAG = " + lag);
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    
    private final String monitoringConsumerGroupID = "monitoring_consumer_" + UUID.randomUUID().toString();
    
    public Map<TopicPartition, PartionOffsets> getConsumerGroupOffsets(String host, String topic, String groupId) {
        Map<TopicPartition, Long> logEndOffset = getLogEndOffset(topic, host);
    
        Set<TopicPartition> topicPartitions = new HashSet<>();
        for (Entry<TopicPartition, Long> s : logEndOffset.entrySet()) {
            topicPartitions.add(s.getKey());
        }
        
        KafkaConsumer<String, Object> consumer = createNewConsumer(groupId, host);
        Map<TopicPartition, OffsetAndMetadata> comittedOffsetMeta = consumer.committed(topicPartitions);
    
        BinaryOperator<PartionOffsets> mergeFunction = (a, b) -> {
            throw new IllegalStateException();
        };
        Map<TopicPartition, PartionOffsets> result = logEndOffset.entrySet().stream()
                .collect(Collectors.toMap(entry -> (entry.getKey()), entry -> {
                    OffsetAndMetadata committed = comittedOffsetMeta.get(entry.getKey());
                    long currentOffset = 0;
                    if(committed != null) { //committed offset will be null for unknown consumer groups
                        currentOffset = committed.offset();
                    }
                    return new PartionOffsets(entry.getValue(), currentOffset, entry.getKey().partition(), topic);
                }, mergeFunction));
    
        return result;
    }
    
    public Map<TopicPartition, Long> getLogEndOffset(String topic, String host) {
        Map<TopicPartition, Long> endOffsets = new ConcurrentHashMap<>();
        KafkaConsumer<?, ?> consumer = createNewConsumer(monitoringConsumerGroupID, host);
        List<PartitionInfo> partitionInfoList = consumer.partitionsFor(topic);
        List<TopicPartition> topicPartitions = partitionInfoList.stream()
                .map(pi -> new TopicPartition(topic, pi.partition())).collect(Collectors.toList());
        consumer.assign(topicPartitions);
        consumer.seekToEnd(topicPartitions);
        topicPartitions.forEach(topicPartition -> endOffsets.put(topicPartition, consumer.position(topicPartition)));
        consumer.close();
        return endOffsets;
    }
    
    private static KafkaConsumer<String, Object> createNewConsumer(String groupId, String host) {
        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, host);
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        return new KafkaConsumer<>(properties);
    }
    
    private static class PartionOffsets {
        private long lag;
        private long timestamp = System.currentTimeMillis();
        private long endOffset;
        private long currentOffset;
        private int partion;
        private String topic;
    
        public PartionOffsets(long endOffset, long currentOffset, int partion, String topic) {
            this.endOffset = endOffset;
            this.currentOffset = currentOffset;
            this.partion = partion;
            this.topic = topic;
            this.lag = endOffset - currentOffset;
        }
    
        @Override
        public String toString() {
            return "PartionOffsets [lag=" + lag + ", timestamp=" + timestamp + ", endOffset=" + endOffset
                    + ", currentOffset=" + currentOffset + ", partion=" + partion + ", topic=" + topic + "]";
        }
    
    }
    }
    

    【讨论】:

      猜你喜欢
      • 2019-01-13
      • 2019-09-27
      • 2019-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-08
      相关资源
      最近更新 更多