KafkaConsumer 指定消费位置的基础
一个 Topic 对应着磁盘上的几个重要的文件:
- .log:数据文件,存储了该 topic 中的所有消息。
- .index:索引文件,对数据文件中的消息进行索引。关键就是可以按照 offset 来索引
- .timeindex:时间索引文件,类似于索引文件,但按照消息的时间戳进行索引。它保存着 timestamp-offset 的关系,所以可以使用时间戳找到对应的 offset ,然后再找到对应的 offset 位置,这样就能找到对应的文件开始的位置了。
- leader-epoch-checkpoint:该文件的职责是存储leader对于topic的进度,以便在leader重启时可以重新定位到上一次成功提交的offset位置。
在我们探讨的功能中,index和timeindex两个文件扮演着关键角色,它们与消费者开始消费的位置密切相关。这是我们今天要讲解的核心基础,没有这两个文件,我们无法进行后续的操作。
具体的做法
方法名 | 用法 | 业务意义 |
---|---|---|
subscribe() | consumer.subscribe(Arrays.asList(“topic1”, “topic2”)); | 订阅一个或多个主题,按照策略指定当前消费者实例消费那个分区的数据 |
assign() | TopicPartition partition0 = new TopicPartition(“test”, 0); consumer.assign(Arrays.asList(partition0)); |
指定分配的TopicPartition,手工指定当前消费者实例消费那个分区的数据 |
assignment() | Set partitions = consumer.assignment(); | 获取当前已分配的TopicPartition列表 |
beginningOffsets() | Map<TopicPartition, Long> beginningOffsets = consumer.beginningOffsets(Arrays.asList(partition0)); | 获取指定TopicPartition的起始偏移量信息 |
说明:
subscribe()
和assign()
两个方法的使用是互斥的,只能使用其中之一。assignment()
方法只能在 consumer 已经订阅主题或手动分配分区之后使用,否则返回空列表。
在执行subscribe()
和 assign()
之后,assignment()
才能获取到偏移的位置,否返回一个 size() = 0 的 map。更加让人不能接收的是执行subscribe()
之后,仍然assignment()
不能获取 TopicPartition 信息。需要在执行了 poll() 方法之后才行。这就比较扯了。我本来想指定一个开始位置,但是还没有指定位置呢?先让我消费出来点数据,这不是多余吗?
在 FlinkKafkaConsumer 类中,使用了 assign()
+ seek
的方式,指定了消费者的消费位置,这样以来, __consumer_offsets
就有用了,如果 FlinkKafkaConsumer 不是从 checkpoint 消费,则可以指定 Consumer 的 startMap 模式,在不同的模式下,可以从最早、最新、上次消费的位置来开始消费。
FlinkKafkaConsumer 中的 startMode 参数表示消费者的启动模式,具体有以下几种:
startMode | 中文名称 | 业务意义 |
---|---|---|
GROUP_OFFSETS | 消费者组偏移量 | 从消费者组中最近提交的偏移量开始消费,保证消费者组的偏移量和Kafka中的主题分区的偏移量保持一致,避免出现重复消费或者漏消费的情况 |
EARLIEST | 最早的 | 从最早的消息开始消费,即消费者会从最早的偏移量开始消费Kafka中的消息 |
LATEST | 最新的 | 从最新的消息开始消费,即消费者会从最新的偏移量开始消费Kafka中的消息 |
TIMESTAMP | 时间戳 | 从指定的时间戳开始消费Kafka中的消息 |
SPECIFIC_OFFSETS | 指定偏移量 | 从指定的偏移量开始消费Kafka中的消息 |
一般情况下,我们会使用 GROUP_OFFSETS 模式来消费 Kafka 中的消息,因为这样可以保证消费者组的偏移量和 Kafka 中的主题分区的偏移量保持一致,避免出现重复消费或者漏消费的情况。如果需要从最早或者最新的消息开始消费,可以选择 EARLIEST 或者 LATEST 模式。如果需要从指定的时间戳或者偏移量开始消费,可以选择 TIMESTAMP 或者 SPECIFIC_OFFSETS 模式。
关键代码
以下是加上注释的代码:
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.PartitionInfo;
import org.apache.kafka.common.TopicPartition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
public class KafkaConsumerAt {
private static Logger logger = LoggerFactory.getLogger(KafkaConsumerAt.class);
public static void main(String[] args) throws InterruptedException {
Properties p = new Properties();