Kafka简介、从kafka中动态获取数据(指定获取条数)、动态设置偏移量、优化kafka调用时间

目录

1.Kafka简介

2.kafka的相关配置

3. 需求:从kafka中动态获取数据,可指定条数

4.功能点:

5.实现步骤:

5-1、kafka的配置

5-2、初版代码功能实现

5-3、终版代码功能实现-对初版代码进行优化


1. Kafka简介

Kafka是一种消息队列,主要用来处理大量数据状态下的消息队列,一般用来做日志的处理。既然是消息队列,那么Kafka也就拥有消息队列的相应的特性了。

2.kafka的相关配置

配置作用
auto.commit.interval.ms自动提交偏移量的时间间隔,当消费者启用自动提交偏移量功能时,它会定期地将当前的偏移量提交到 Kafka 服务器,以确保消费者的进度被持久  auto.commit.interval.ms 参数指定了自动提交的时间间隔,单位是毫秒。
auto.offset.reset

当消费者启动时或偏移量无效时,如何处理偏移量,默认值为earliest。

auto.offset.reset 的可选值包括:

  • earliest:表示消费者会从最早的可用消息开始消费。即,如果没有保存的偏移量,则从主题的起始处开始消费。
  • latest:表示消费者会从最新的消息开始消费。即,如果没有保存的偏移量,则只消费新产生的消息。
  • none:表示如果没有保存的偏移量,则会抛出异常给消费者,而不会自动重置偏移量。
bootstrap.serverskafka服务器的地址
check.crcs指定是否检查消息的CRC32,以确保消息的完整性
client.id客户端标识符,用于在服务器日志中追踪请求源
connections.max.idle.ms连接在关闭之前那可以保持空闲的最大时间,单位为毫秒,默认9分钟
default.api.timeout.ms默认api调用的超时时间,单位为毫秒,该参数用于定义 Kafka 客户端发起 API 请求时的默认超时时间,适用于消费者、生产者、管理员客户端等。超时时间的设定有助于防止操作因网络或系统问题而无限期地挂起。在达到这个超时时间后,如果请求没有成功完成,操作会终止并返回超时错误。
enable.auto.commit是否启用自动提交偏移量
exclude.internal.topics是否在自动偏移量管理中排除内部主题,内部主题是 Kafka 内部使用的特殊主题,通常用于存储一些系统元数据信息或者日志。这些主题对于普通应用程序来说并不需要被消费,因此可以通过设置 exclude.internal.topics 参数来排除它们,避免消费者拉取这些内部主题的消息。
fetch.max.bytes每个分区从服务器获取的最大数据量,单位为字节
fetch.max.wait.ms在发出拉取请求之前等待数据可用的最长时间,单位为毫秒,当消费者调用拉取消息的 API 时,它会指定一个等待时间,即在这段时间内等待服务器响应, fetch.max.wait.ms 属性定义了消费者在一次拉取请求中最长的等待时间。如果在这个时间内没有足够的数据可供拉取,则消费者将会收到较小数量的消息或者空响应。
fetch.min.bytes每个分区从服务器获取的最小数据量,单位为字节
group.id消费者所属的消费者组标识符
heartbeat.interval.ms心跳间隔的频率,用于维持与消费者组协调器的活动连接,消费者在与消费者组协调器(Consumer Group Coordinator)通信时,会定期发送心跳以表明自己的存活状态。这些心跳用于告知协调器消费者仍然处于活动状态。heartbeat.interval.ms 属性定义了发送心跳的频率,即消费者多久发送一次心跳。
isolation.levelisolation.level 参数用于设置消费者的隔离级别,它决定了消费者在读取消息时能够看到的数据。默认情况下,isolation.level 的值是 read_uncommitted,即读取未提交的数据
key.deserializer键的反序列化
max.partition.fetch.bytes单次调用 poll() 可以返回的最大数据量,控制消费者在单次拉取请求中可以获取的单个分区的最大数据量(以字节为单位),默认值是 1MB(1,048,576 字节),当消费者从 Kafka 服务器拉取消息时,它会向服务器发送一个拉取请求,指定要拉取的分区以及每个分区的偏移量和最大拉取字节数。max.partition.fetch.bytes 属性就是用来限制每个分区拉取的最大字节数。如果某个分区的消息大小超过了这个限制,那么该分区的消息可能会被截断,消费者只能获取部分消息。
max.poll.interval.ms消费者处理消息的最大时间间隔,用于检测消费者故障
max.poll.records每次调用poll()最多返回的记录数,默认500条
metadata.max.age.ms元数据的最大缓存时间,用于控制消费者是否更新分区元数据,消费者在启动时会从服务器获取最新的元数据信息,以便知道从哪些分区拉取消息。metadata.max.age.ms 属性定义了消费者在多久之后应该重新获取元数据信息。在这段时间内,消费者将会重用之前获取的元数据信息,而不会向服务器发起新的元数据请求。

3. 需求:从kafka中动态获取数据,可指定条数

4.功能点:

    1.kafka的配置
    2.如何从kafka中获取数据,如何从kafka中拉取数据
    3.如何获取指定的条数的数据
    4.如何只提交“获取数据”的偏移量

5.实现步骤:

5-1、kafka的pom文件
<!-- Spring Kafka -->
<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>
<!-- Spring for Apache Kafka -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-integration</artifatId>
</dependency>
<!-- Kafka Clients -->
<dependency>   <groupId>org.apache.kafka</groupId>
    <artifactId>kafka-clients</artifactId>
</dependency>
package com.xldatacloud.monitorserverapi.config;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.Properties;


@Component
public class KafkaConfig {

    private static Logger logger = Logger.getLogger(KafkaConfig.class);

    public static KafkaConsumer<String, String> consumer;

    @Value("${spring.kafka.bootstrap-servers}")
    public    String bootstrapServers;

    public  String groupId = "subscriptionConsumer";

    public  String topic = "subject_9920";

    @Value("${spring.kafka.consumer.enable-auto-commit}")
    public  boolean enableAutoCommit;

    @Value("${spring.kafka.consumer.auto-offset-reset}")
    public  String autoOffsetReset;

    @Value("${spring.kafka.consumer.properties.sasl.jaas.config}")
    public  String saslJaasConfig;

    @Value("${spring.kafka.consumer.properties.sasl.mechanism}")
    public  String saslMechanism;

    @Value("${spring.kafka.consumer.properties.security.protocol}")
    public  String securityProtocol;

    public  final String SASL_JAAS_CONFIG = "sasl.jaas.config";

    public  final String SASL_MECHANISM = "sasl.mechanism";

    public  final String SECURITY_PROTOCOL = "security.protocol";

    @PostConstruct
    public void init() {
        initDATA(groupId,topic);
    }

    public void updateConfig(String groupId, String topic) {
        // 初始化新的 KafkaConsumer 实例
        initDATA(groupId, topic);
    }

    public void initDATA(String groupId, String topic) {
        logger.info("当前groupId为"+groupId+",当前topic为"+topic);
        // 初始化Kafka消费者
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, enableAutoCommit);
        props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, autoOffsetReset);
        props.put(SASL_JAAS_CONFIG, saslJaasConfig);
        props.put(SASL_MECHANISM, saslMechanism);
        props.put(SECURITY_PROTOCOL, securityProtocol);
        props.put("max.poll.records", "100"); // 默认设置为500条记录
        consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Arrays.asList(topic));
    }

    public KafkaConsumer<String, String> getConsumer() {
        return consumer;
    }
}
5-2、初版代码功能实现

初版代码存在得问题:接口调用时间过长,大致在2~3秒,分析后得知,当前代码每次在接口调用的时候,都会新建消费者实例,导致接口调用之间过长,在终版代码中已解决这个问题,已将时间优化到100ms以内。

package com.xldatacloud.monitorserverapi.controller;

import com.alibaba.excel.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xldatacloud.monitorserverapi.common.LocalData;
import com.xldatacloud.monitorserverapi.common.ResultEntity;
import com.xldatacloud.monitorserverapi.config.KafkaConfig;
import com.xldatacloud.monitorserverapi.entity.datasubscription.Subscription;
import com.xldatacloud.monitorserverapi.utils.DateHelper;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.TopicPartition;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@RestController
public class KafkaConsumerController {

    private static Logger logger = Logger.getLogger(KafkaConsumerController.class);

    private static KafkaConsumer<String, String> consumer;

    private final KafkaConfig kafkaConfig;

    @Value("${subscription.data.send.count}")
    private int sendCount;

    @Value("${spring.kafka.consumer.group-id}")
    private String groupId;


    public KafkaConsumerController(KafkaConfig kafkaConfig) {
        this.kafkaConfig = kafkaConfig;
        this.consumer = kafkaConfig.getConsumer();
    }


    @PostMapping(test/list")
    public ResultEntity consumeKafka(@RequestBody String param) {
        String date = DateHelper.getCurrentDateTime();
        try {
            JSONObject jsonObject = JSONObject.parseObject(param);
            Integer count = (Integer)jsonObject.get("count");
            String organization = (String)jsonObject.get("organization");
            logger.info("推送数据开始,调用时间为" + date);
            logger.info("客户传递的参数: count = " + count + ", organization = " + organization);
            // 对数据进行校验
            ResultEntity checkResult = checkData(organization);
            if (checkResult != null) return checkResult;
            // 动态设定groupId、topic
            kafkaConfig.updateConfig((groupId + organization), ("subject_" + organization));
            this.consumer = kafkaConfig.getConsumer();
            // 如果没传返回条数大小,则设置为默认10
            sendCount = count == null ? sendCount : count;
            // 如果count不在0到200之间,返回一个包含错误信息的Result对象
            if (count != null && (count <= 0 || count > 200)) {
                return ResultEntity.fail(-10001, "发送条数必须在1到200之间");
            }
            logger.info("本次返回条数限定为【 " + sendCount + " 】条");
            List<String> msgList = new ArrayList<>();
            Map<TopicPartition, OffsetAndMetadata> offsetsToCommit = new HashMap<>();
            // 获取kafka中的数据
            List<ConsumerRecord<String, String>> allRecords = new ArrayList<>();
            logger.info("从kafka中获取数据-----》开始");
            long startTime = System.currentTimeMillis();
            int i =0;
            Iterable<ConsumerRecord<String, String>> recordList = consumer.poll(Duration.ofSeconds(10)).records("subject_" + organization);
            for (ConsumerRecord<String, String> record : recordList) {
                ConsumerRecord<String, String> consumerRecord = new ConsumerRecord<>(record.topic(),record.partition(),record.offset(),record.key(),record.value());
                allRecords.add(consumerRecord);
                if (sendCount == ++i){
                    break;
                }
            }
            long endTime = System.currentTimeMillis();
            logger.info("从kafka中获取数据-----》结束");
            logger.info("----------从kafka服务器上获取数据总用时----------"+(endTime-startTime)+"ms");
            logger.info("对获取的数据进行json转换-----》开始");
            startTime = System.currentTimeMillis();
            for (ConsumerRecord<String, String> record : allRecords) {
                // json转换
                Subscription subscription = JSON.parseObject(record.value(), Subscription.class);
                String sendJsonData = JSON.toJSONString(subscription);
                msgList.add(sendJsonData);
                // 提交当前记录的偏移量
                TopicPartition topicPart = new TopicPartition(record.topic(), record.partition());
                long nextOffset = record.offset() + 1;
                offsetsToCommit.put(topicPart, new OffsetAndMetadata(nextOffset));
                // 每次推送的数据量限制
                if (msgList.size() == sendCount) {
                    break;
                }
            }
            endTime = System.currentTimeMillis();
            logger.info("对获取的数据进行json转换-----》结束");
            logger.info("----------对获取的数据进行数据封装总用时----------"+(endTime-startTime)+"ms");
            // 如果存在偏移量,则提交;记录接口调用次数
            if (!offsetsToCommit.isEmpty()) {
                consumer.commitSync(offsetsToCommit);
                logger.info("推送数据,推送数量为:" + msgList.size() + ",推送的数据为:" + msgList);
            }
            return ResultEntity.ok(msgList);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("推送的数据发生异常,调用时间为" + date + ",报错原因为" + e);
            return ResultEntity.fail(-1, "请求失败");
        } finally {
            logger.info("推送数据结束,调用时间为" + date);
            consumer.close();
        }
    }

    /**
     * 数据校验
     * @param organization
     * @return
     */
    private static ResultEntity checkData(String organization) {
        // 校验机构id是否传递
        if (StringUtils.isBlank(organization)) {
            logger.info("机构id未传递");
            return ResultEntity.fail(-10001, "机构id未传递");
        }
        // 从threadlocl中获取机构信息
        List<Long> orgList = LocalData.getOrgList();
        // 校验传递的机构id是否正确
        boolean flag = orgList.stream().anyMatch(org -> org.equals(Long.parseLong(organization)));
        if (!flag) {
            logger.info("机构信息不匹配,请确认机构id是否正确");
            return ResultEntity.fail(-10001, "机构信息不匹配,请确认机构id是否正确");
        }
        return null;
    }
}
5-3、终版代码功能实现-对初版代码进行优化(提高响应速度)

优化接口调用时间,目前接口调用已经提到100ms以内,在多次进行接口调用时,只需第一次新建消费者实例,后续不在新建消费者,避免了客户端与服务器连接的耗时问题。

import com.alibaba.excel.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xldatacloud.monitorserverapi.common.LocalData;
import com.xldatacloud.monitorserverapi.common.ResultEntity;
import com.xldatacloud.monitorserverapi.config.KafkaConfig;
import com.xldatacloud.monitorserverapi.entity.datasubscription.Subscription;
import com.xldatacloud.monitorserverapi.utils.DateHelper;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;


@RestController
@RequestMapping("test")
public class KafkaConsumerController {

    private static Logger logger = Logger.getLogger(KafkaConsumerController.class);

    public static ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>();

    private static KafkaConsumer<String, String> consumer;

    private final KafkaConfig kafkaConfig;

    @Value("${spring.kafka.consumer.group-id}")
    private String groupId;

    public KafkaConsumerController(KafkaConfig kafkaConfig) {
        this.kafkaConfig = kafkaConfig;
        this.consumer = kafkaConfig.getConsumer();
    }

    @PostMapping("/list")
    public ResultEntity consumeKafka(@RequestBody String param) {
        String date = DateHelper.getCurrentDateTime();
        try {
            logger.info("推送数据开始,调用时间为" + date);
            JSONObject jsonObject = JSONObject.parseObject(param);
            String organization = (String) jsonObject.get("organization");
            // 增加一个标识,方便做测试
            String tag = (String) jsonObject.get("tag");
            logger.info("客户传递的参数:" + "organization = " + organization);
            // 对数据进行校验
            ResultEntity checkResult = checkData(organization);
            if (checkResult != null) return checkResult;
            // 动态设定groupId、topic,增加这个tag方便做测试
            String key = "kafka_consumer_" + organization + tag;
            if (map.get(key) == null) {
                map.put(key, "organization");
                kafkaConfig.updateConfig((groupId + organization + tag), ("subject_" + organization));
                this.consumer = kafkaConfig.getConsumer();
            }
            // 从kafka中获取数据,并进行json转换
            List<String> msgList = getList(organization);
            // 提交偏移量
            consumer.commitSync();
            logger.info("本次推送数量为:" + msgList.size() + ",推送的数据为:" + msgList);
            return ResultEntity.ok(msgList);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("推送的数据发生异常,调用时间为" + date + ",报错原因为" + e);
            return ResultEntity.fail(-1, "请求失败");
        } finally {
            logger.info("推送数据结束,调用时间为" + date);
        }
    }

    /**
     * 从kafka中获取数据,并进行json转换
     *
     * @param organization
     * @return
     */
    private static List<String> getList(String organization) {
        List<String> msgList = new ArrayList<>();
        // 获取kafka中的数据
        List<ConsumerRecord<String, String>> allRecords = new ArrayList<>();
        logger.info("从kafka中获取数据-----》开始");
        long startTime = System.currentTimeMillis();
        Iterable<ConsumerRecord<String, String>> recordList = consumer.poll(Duration.ofSeconds(10)).records("subject_" + organization);
        for (ConsumerRecord<String, String> record : recordList) {
            ConsumerRecord<String, String> consumerRecord = new ConsumerRecord<>(record.topic(), record.partition(), record.offset(), record.key(), record.value());
            allRecords.add(consumerRecord);
        }
        long endTime = System.currentTimeMillis();
        logger.info("----------从kafka服务器上获取数据总用时----------" + (endTime - startTime) + "ms");
        logger.info("从kafka中获取数据-----》结束");
        logger.info("对获取的数据进行json转换-----》开始");
        startTime = System.currentTimeMillis();
        msgList = allRecords.stream().map(record -> {
            Subscription subscription = JSON.parseObject(record.value(), Subscription.class);
            return JSON.toJSONString(subscription);
        }).collect(Collectors.toList());
        endTime = System.currentTimeMillis();
        logger.info("----------进行json数据封装总用时----------" + (endTime - startTime) + "ms");
        logger.info("对获取的数据进行json转换-----》结束");
        return msgList;
    }

    /**
     * 数据校验
     *
     * @param organization
     * @return
     */
    private static ResultEntity checkData(String organization) {
        // 校验机构id是否传递
        if (StringUtils.isBlank(organization)) {
            logger.info("机构id未传递");
            return ResultEntity.fail(-10001, "机构id未传递");
        }
        // 从ThreadLocal中获取机构信息
        List<Long> orgList = LocalData.getOrgList();
        logger.info("本人所包含的机构为: " + orgList);
        // 校验传递的机构id是否正确
        boolean flag = orgList.stream().anyMatch(org -> org.equals(Long.parseLong(organization)));
        if (!flag) {
            logger.info("机构信息不匹配,当前客户端传递的机构id为 " + organization);
            return ResultEntity.fail(-10001, "机构信息不匹配,请确认机构id是否正确");
        }
        return null;
    }
}

  • 9
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值