offset 的维护是 Consumer 消费数据是必须考虑的问题。
1、KafkaConsumer:需要创建一个消费者对象,用来消费数据
ConsumerConfig:获取所需的一系列配置参数
ConsuemrRecord:每条数据都要封装成一个 ConsumerRecord 对象
2、自动提交 offset 的相关参数:
enable.auto.commit:是否开启自动提交 offset 功能
auto.commit.interval.ms:自动提交 offset 的时间间隔
props.put(“group.id”, “test”);
auto.offset.reset:当新来一个组,或者当前的offset不存在了
earliest:从开始消费
latest:默认值,从最新的开始消费
poll 拉取数据的超时时间
3、手动提交 offset 的方法有两种:分别是 commitSync(同步提交)和 commitAsync(异步提交)。两者的相同点是,都会将本次 poll 的一批数据最高的偏移量提交;不同点是,commitSync 阻塞当前线程,一直到提交成功,并且会自动失败重试(由不可控因素导致,也会出现提交失败);而 commitAsync 则没有失败重试机制,故有可能提交失败。
props.put(“enable.auto.commit”, “false”);//关闭自动提交 offset
在while循环里
(1)同步提交:consumer.commitSync();
(2)异步提交:
consumer.commitAsync(new OffsetCommitCallback() {
@Override
public void onComplete(Map<TopicPartition, OffsetAndMetadata> offsets, Exception exception) {
if (exception != null) {
System.err.println("Commit failed for" + offsets); } }
});
4、自定义存储 offset
offset 的维护是相当繁琐的,因为需要考虑到消费者的 Rebalace。
当有新的消费者加入消费者组、已有的消费者推出消费者组或者所订阅的主题的分区发生变化,就会触发到分区的重新分配,重新分配的过程叫做 Rebalance。消费者发生 Rebalance 之后,每个消费者消费的分区就会发生变化。因此消费者要首先获取到自己被重新分配到的分区,并且定位到每个分区最近提交的 offset 位置继续消费。
要实现自定义存储 offset,需要借助 ConsumerRebalanceListener,以下为示例代码,其
中提交和获取 offset 的方法,需要根据所选的 offset 存储系统自行实现。
private static Map<TopicPartition, Long> currentOffset = new HashMap<>();
//消费者订阅主题
consumer.subscribe(Arrays.asList("first"), new ConsumerRebalanceListener() {
//该方法会在 Rebalance 之前调用
@Override
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
commitOffset(currentOffset);
}
//该方法会在 Rebalance 之后调用
@Override
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
currentOffset.clear();
for (TopicPartition partition : partitions) {
//定位到最近提交的 offset 位置继续消费
consumer.seek(partition, getOffset(partition));
}
}
});
while (true) {
//poll 拉取数据的超时时间
ConsumerRecords<String, String> records = consumer.poll(100);//消费者拉取数据
for (ConsumerRecord<String, String> record : records) {
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
currentOffset.put(new TopicPartition(record.topic(), record.partition()), record.offset());
}
commitOffset(currentOffset);//异步提交
}
//获取某分区的最新 offset
private static long getOffset(TopicPartition partition) {
return 0;
}
//提交该消费者所有分区的 offset
private static void commitOffset(Map<TopicPartition, Long> currentOffset) {
}
5、列出所有topic
Map<String, List<PartitionInfo>> topics = consumer.listTopics();
6、得到offset
public static Map<TopicPartition, Long> getOffsetFromKafka(String topic){
KafkaConsumer<String, String> consumer=KafkaManager.getConsumer();
Collection<PartitionInfo> partitionInfos = consumer.partitionsFor(topic);
ArrayList<TopicPartition> partitions=new ArrayList<>();
partitionInfos.forEach(info->{
int partitionId=info.partition();
System.out.println("partitionId:"+partitionId);
TopicPartition topicPartition=new TopicPartition(topic, partitionId);
partitions.add(topicPartition);
});
//得到每个partition的endoffset
Map<TopicPartition, Long> endoffsetMap=consumer.endOffsets(partitions);
//得到每个partition的beginendoffset
Map<TopicPartition, Long> beginoffsetMap=consumer.beginningOffsets(partitions);
for (Map.Entry<TopicPartition, Long> entry:beginoffsetMap.entrySet()) {
System.out.println("beginoffsetMap,key:"+entry.getKey().toString()+",value:"+entry.getValue());
}
return endoffsetMap;
7、从头开始消费
consumer.subscribe(Arrays.asList(topic), new ConsumerRebalanceListener() {
@Override
public void onPartitionsRevoked(Collection<TopicPartition> collection) {
}
@Override
public void onPartitionsAssigned(Collection<TopicPartition> collection) {
consumer.seekToBeginning(collection);
}
});
7、consumer消费时由两种API
assign的consumer不会拥有kafka的group management机制。
8、三种控制消费位置的方式
9、subscribe
subscribe(Collection, ConsumerRebalanceListener)
消费指定的partition;seek方法
手动管理offset时,可以在rebalance结束前提交offset
ConsumerRebalanceListener,如果用assign,就失效了
(1)onPartitionsRevoked :优先级高,手动保存offset,可以提交到kafka或者MySQL等,only called before rebalances
(2)onPartitionsAssigned:在Rebalance后,重新消费前
10、group management rebalance