KAFKA消费者-进阶用法

Apache Kafka 是一个分布式流处理平台,用于构建实时流数据管道和应用程序。在 Kafka 中,消费者(Consumer)用于从 Kafka 主题(Topic)中读取消息并进行处理。本文将介绍 Kafka 消费者的进阶用法,包括手动提交偏移量、消费者组、重新平衡等功能。

一. 创建 Kafka 消费者

首先,我们需要创建 Kafka 消费者,并配置消费者属性。

而创建消费在方式有俩种,一种是注解,另一种则是通过new KafkaConsumer()的方式来获取到消费者实例;

1.注解:

@KafkaListener(topic = "your-topic")
    public void handle(ConsumerRecord consumerRecord) {
        System.out.println("消费者消费消息:" + consumerRecord);
        System.out.println(String.format("消费者收到消息,topic:%s,partition:%s", consumerRecord.topic(), consumerRecord.partition()));
        System.out.println("消费内容:" + consumerRecord.value());
    }

@KafkaListener(topics = {"your-topic1", "your-topic2"})
    public void handleCMDB(ConsumerRecord consumerRecord) {
        System.out.println("消费者消费消息:" + consumerRecord);
        System.out.println(String.format("消费者收到消息,topic:%s,partition:%s", consumerRecord.topic(), consumerRecord.partition()));
        logger.info(String.format("消费者收到消息,topic:%s,partition:%s", consumerRecord.topic(), consumerRecord.partition()));
        System.out.println("消费内容:" + consumerRecord.value());
    }

 使用注解通过topic=指定需要监听的通道,同时可以使用topics监听多个通道,通过consumerRecord.value()即可获取到通道中的数据值;而kafka的配置需要在yml配置文件中指定

2.new KafkaConsumer()实例:

使用创建实例的方式需要指定properties配置,否则获取到的消费者实例为空,没有任何意义,而创建消费者来进行消费的方法有俩种,一种方式必须指定groupid,一种方式是默认会指定groupid不需要手动指定,以下是代码示例:

// 方式1:需要指定gruopid
public String useMsg(){
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "yourKafkaServers");
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "defaultConsumerGroup"); // 默认组
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        consumer.subscribe(Collections.singletonList("kafka_test_topic"));

        try {
            while (true) {
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
                for (ConsumerRecord<String, String> record : records) {
                    System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
                }
            }
        } catch (WakeupException e) {
            // Ignore exception for shutdown
        } finally {
            consumer.close();
        }
        return "消费成功";
    }

// 方式2:无需手动指定gruopid
public String useOtherMsg(){
        Properties props = new Properties();
        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "yourKafkaServers");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());

        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
        // 使用TopicPartition指定消费者主题和分区
        TopicPartition topicPartition = new TopicPartition("yourtopic", 0);
        consumer.assign(Arrays.asList(topicPartition));

        try {
            while (true) {
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
                for (ConsumerRecord<String, String> record : records) {
                    System.out.println("消费消息!!!!");
                    System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
                }
            }
        } catch (WakeupException e) {
            // Ignore exception for shutdown
        } finally {
            consumer.close();
        }
        return "无group消费者消费数据";
    }

二. 手动提交偏移量

手动提交偏移量可以确保消息被成功处理后再提交偏移量,避免消息丢失或重复消费。

设置偏移量有一种简单粗暴的方式:

consumer.seek(topicPartition, 0);

直接将偏移量设置为0,即从队列最开始的地方读取数据

第二种方式根据时间戳来设置偏移量,代码如下:

TopicPartition topicPartition = new TopicPartition("yourtopic", 0);
            consumer.assign(Arrays.asList(topicPartition));
            // 指定偏移量开始时间戳,默认为当天00:00的时间戳
            long timestampToSearch = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toEpochSecond() * 1000;
            if (kafkaConfig.getLastRunTime() != null) {
                // 默认十秒提前量容错
                long advanceTime = kafkaConfig.getAdvanceTime() != null ? kafkaConfig.getAdvanceTime() : 10000;
                timestampToSearch = kafkaConfig.getLastRunTime().getTime() - advanceTime;
            }
            logger.info("设置当前偏移量开始时间戳:" + timestampToSearch);
            // 查找每个分区在指定时间的偏移量
            Map<TopicPartition, Long> timestampsToSearch = new HashMap<>();
            timestampsToSearch.put(topicPartition, timestampToSearch);
            Map<TopicPartition, OffsetAndTimestamp> offsetsForTimes = consumer.offsetsForTimes(timestampsToSearch);
            // 将消费者指针移动到指定时间的偏移量
            for (Map.Entry<TopicPartition, OffsetAndTimestamp> entry : offsetsForTimes.entrySet()) {
                TopicPartition partition = entry.getKey();
                OffsetAndTimestamp offsetAndTimestamp = entry.getValue();
                if (offsetAndTimestamp != null) {
                    consumer.seek(partition, offsetAndTimestamp.offset());
                }
            }

这里的getLastRunTime即为获取到上次从队列中退出时的时间,advanceTime为容错时间,即退出时间需要往前推迟多久,这样可能会读取到重复的一俩条数据,但并不会丢失数据,有利有弊且利大于弊;有细心读者会发现这里的队列设置偏移量是使用的不需要指定groupid的消费者,那么指定groupid的消费者该如何设置偏移量呢?别急,下面就给你们端上来:
 

consumer.subscribe(Collections.singletonList("yourtopic"), new ConsumerRebalanceListener() {
                @Override
                public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
                    // 分区被撤销时的处理,这里可以清空任何与这些分区相关的信息
                }

                @Override
                public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
                    // 分区被分配时的处理
                    TopicPartition topicPartition = new TopicPartition("yourtopic", 0);
                    // 指定偏移量开始时间戳,默认为当天00:00的时间戳
                    long timestampToSearch = LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toEpochSecond() * 1000;
                    if (kafkaConfig.getLastRunTime() != null) {
                        // 默认十秒提前量容错
                        long advanceTime = kafkaConfig.getAdvanceTime() != null ? kafkaConfig.getAdvanceTime() : 10000;
                        timestampToSearch = kafkaConfig.getLastRunTime().getTime() - advanceTime;
                    }
                    logger.info("设置当前偏移量开始时间戳:" + timestampToSearch);
                    Map<TopicPartition, Long> timestampsToSearch = Collections.singletonMap(topicPartition, timestampToSearch);
                    Map<TopicPartition, OffsetAndTimestamp> offsetsForTimes = consumer.offsetsForTimes(timestampsToSearch);
                    for (Map.Entry<TopicPartition, OffsetAndTimestamp> entry : offsetsForTimes.entrySet()) {
                        OffsetAndTimestamp offsetAndTimestamp = entry.getValue();
                        if (offsetAndTimestamp != null) {
                            consumer.seek(entry.getKey(), offsetAndTimestamp.offset());
                        }
                    }
                }
            });

通过内部类,重写了ConsumerRebalanceListener 接口的实现来处理分区的重新分配情况,同时根据时间戳重新设置偏移量,这也是所谓的重新平衡,以实现从指定时间点开始消费消息的功能

三. 消费者组和重新平衡

消费者组允许多个消费者共同消费一个主题,并通过重新平衡(Rebalance)来分配分区给不同的消费者。

props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 1);

consumer.subscribe(Collections.singletonList(TOPIC_NAME));

consumer.poll(0); // 触发消费者加入消费者组
consumer.seekToBeginning(consumer.assignment());

consumeMessages(consumer);

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值