0


kafka查询offset&生产者offset计算&消费offset计算

本文目录

kafka查询offset&生产者offset计算&消费offset计算

1、简介

​ kafka的介绍:略…(有兴趣的同学可自行Google,这与本文无关 ^ _ ^)

2、需求背景

​ 对kafka做监控,需要获取到kafka接收到消息的offset和被消费者消费掉消息的offset,编写接口将数值交给prometheus,直接观察判断kafka的消费性能如何。(如何自定义prometheus的监控指标后续再更新,本章只讲如何获得kafka的offset)

3、前期准备

​ 我们需要在自己的机器上装一个zookeeper和kafka,并测试kafka可用(具体怎么装,请自行搜索)

4、获取kafka生产者的offset以及消费者的offset

​ kafka是有两个offset的,在kafka结构图中,生产者生产消息到kafka后,kafka会记录一次生产者消息的offset,消费者消费掉消息后,kafka会记录一次消费者消费消息的offset,这两个offset是分开的,过程如下图:(大概意思是这样的)
在这里插入图片描述

值得注意的是生产者和消费者的差值就是积压的消息量。(生产者的offset一定是大于等于消费者offset的)

​ 我们再来回顾一下kafka的命令行,如图:

在这里插入图片描述

我们执行kafka提供的命令后可以看到,图中的数据,LOG-END-OFFSET就是当前生产者的offset,CURRENT-OFFSET就是当前已提交的commit offset,也就是消费者的offset,而LAG就是消息积压量。

​ 接下来我们用代码演示一遍(准备小demo,展示演示效果)

项目结构如下:

在这里插入图片描述

代码献上:(每个步骤基本都有注释,有问题的可以私信我)

maven依赖:

<!--kafka依赖--><dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId><version>2.9.0</version></dependency>

配置文件:

server:port:9494kafka:address: localhost:9092group: kclog

代码:

importorg.apache.kafka.clients.admin.AdminClient;importorg.apache.kafka.clients.admin.KafkaAdminClient;importorg.apache.kafka.clients.consumer.Consumer;importorg.apache.kafka.clients.consumer.ConsumerConfig;importorg.apache.kafka.clients.producer.ProducerConfig;importorg.apache.kafka.common.serialization.StringDeserializer;importorg.apache.kafka.common.serialization.StringSerializer;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.kafka.core.DefaultKafkaConsumerFactory;importjavax.annotation.PostConstruct;importjava.util.HashMap;importjava.util.Map;/**
 * @author Java Devin
 * @date 2023/1/8 14:01
 */@ConfigurationpublicclassKafkaConfig{@Value("${kafka.address}")privateString address;@Value("${kafka.group}")privateString group;privateMap<String,Object> producerProp =newHashMap<>();privateMap<String,Object> consumerProp =newHashMap<>();@PostConstructprivatevoidinitProp(){// 生产者配置
        producerProp.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, address);
        producerProp.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,StringSerializer.class);
        producerProp.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class);// 消费者配置
        consumerProp.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, address);
        consumerProp.put(ConsumerConfig.GROUP_ID_CONFIG,group);
        consumerProp.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class);
        consumerProp.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class);}/**
     * 返回kafka客户端
     * @return
     */@BeanpublicAdminClientgetAdminClient(){returnKafkaAdminClient.create(producerProp);}/**
     * 返回一个消费者
     * @return
     */@BeanpublicConsumer<String,String>getConsumer(){DefaultKafkaConsumerFactory<String,String> kafkaConsumerFactory =newDefaultKafkaConsumerFactory<>(consumerProp);return kafkaConsumerFactory.createConsumer();}}

import com.javadevin.config.KafkaConfig;
import com.javadevin.util.KafkaUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.common.KafkaFuture;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 查看 kafka offset 的接口
 * @author Java Devin
 * @date 2023/1/8 14:13
 */
@RestController
public class KafkaOffsetDemo {

    private static final Log log = LogFactory.getLog(KafkaOffsetDemo.class);

    @Autowired
    private KafkaConfig kafkaConfig;

    @Autowired
    private KafkaUtil kafkaUtil;

    @GetMapping("/kafkaOffset")
    public Map<String, Long> testKafkaOffset(){

        // 获取kafka客户端
        AdminClient adminClient = kafkaConfig.getAdminClient();

        // 获取消费者
        Consumer<String, String> consumer = kafkaConfig.getConsumer();

        //获取所有topic名称
        KafkaFuture<Set<String>> topics = adminClient.listTopics().names();
        Set<String> names = null;
        try {
            names = topics.get();
        } catch (Exception e) {
            log.error("kafka topic query error", e);
        }

        //写入kafka的消息总量
        Long endOffset = 0L;
        //从kafka消费的消息总量
        Long commitOffset = 0L;

        // 将每一个topic中的数据累加起来
        for (String name : names) {
            endOffset += kafkaUtil.getTopicEndOffset(consumer, name);
            commitOffset += kafkaUtil.getTopicCommitOffset(consumer, name);
        }

        Map<String, Long> map = new HashMap<>(2);

        map.put("endOffset", endOffset);
        map.put("commitOffset", commitOffset);

        return map;
    }
}

import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.PartitionInfo;
import org.apache.kafka.common.TopicPartition;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * kafka 工具类
 * @author Java Devin
 * @date 2023/1/8 14:16
 */
@Component
public class KafkaUtil {

    /**
     * 获取单个topic的消费量
     * @param consumer
     * @param topic
     * @return
     */
    public Long getTopicCommitOffset(Consumer<String, String> consumer, String topic){
        Long commitOffset = 0L;

        // 这一步是因为 kafka架构的特殊性 在每一个topic中都存在partition 根据每个项目的不同 会配置或不配置partition
        // 消息会分散的存储在partition中 所以我们计算时应该计算所有partition的offset的和
        List<PartitionInfo> partitionInfos = consumer.partitionsFor(topic);
        for (PartitionInfo partitionInfo : partitionInfos) {
            TopicPartition topicPartition = new TopicPartition(partitionInfo.topic(), partitionInfo.partition());
            OffsetAndMetadata committed = consumer.committed(topicPartition);
            if (committed != null){
                commitOffset += committed.offset();
            }
        }
        return commitOffset;
    }

    /**
     * 获取单个topic接收量
     * @param consumer
     * @param topic
     * @return
     */
    public Long getTopicEndOffset(Consumer<String, String> consumer, String topic){
        Long endOffset = 0L;

        // 这一步是因为 kafka架构的特殊性 在每一个topic中都存在partition 根据每个项目的不同 会配置或不配置partition
        // 消息会分散的存储在partition中 所以我们计算时应该计算所有partition的offset的和
        List<PartitionInfo> partitionInfos = consumer.partitionsFor(topic);
        List<TopicPartition> partitions = new ArrayList<>();
        for (PartitionInfo partitionInfo : partitionInfos) {
            TopicPartition topicPartition = new TopicPartition(partitionInfo.topic(), partitionInfo.partition());
            partitions.add(topicPartition);
        }

        Collection<Long> values = consumer.endOffsets(partitions).values();
        for (Long value : values) {
            endOffset += value;
        }
        return endOffset;
    }
}

运行结果展示:

在这里插入图片描述

5、代码测试

我们先来查询一下offset:

在这里插入图片描述

首先使用命令创建一个生产者:

在这里插入图片描述

向kafka发送5条消息,然后再查询一下:
在这里插入图片描述
在这里插入图片描述

现在可以看到接收到消息已经为6,而消费掉的消息还是1,计算一下得出积压了5条消息未消费

现在来开启消费者,再次查询:

在这里插入图片描述
在这里插入图片描述

现在可以根据数据判断出kafka内的消息已经消费完了,再用命令查询一下

在这里插入图片描述

命令查询后,结果是一样的,值得注意的是,我在电脑上配置的kafka只有一个topic,且只会给一个partition中塞数据,创建多个topic或多个partition然后测试的任务就交给大家伙了,我就不一一演示了。

6、总结

​ 其实kafka的offset计算并不难,难点在于很多api如果不去研究官方文档或者请教朋友,就无从得知,并且在计算时topic和partition概念很难一次性消化,但有时间就可以去研究一下,研究明白了会发现其实挺好玩的。

鸡汤送上:每个生命都有裂缝,如此才会有光线射进来。


最后说明,创作不易,若转载请标明出处或原文链接!!!

标签: kafka java 分布式

本文转载自: https://blog.csdn.net/qq_51785096/article/details/128601678
版权归原作者 Java Devin 所有, 如有侵权,请联系我们删除。

“kafka查询offset&生产者offset计算&消费offset计算”的评论:

还没有评论