一:自动提交
//开启offset自动提交
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"false");
缺点:先提交offset后消费,提交完offset后没有消费就挂机后,可能造成丢失数据
二:手动提交
手动提交 offset
的方法有两种:分别是
commitSync
(同步提交)
和
commitAsync
(异步
提交)
。两者的相同点是,都会将
本次
poll
的一批数据最高的偏移量提交
;不同点是,
commitSync
阻塞当前线程,一直到提交成功,并且会自动失败重试(由不可控因素导致,
也会出现提交失败);而
commitAsync
则没有失败重试机制,故有可能提交失败。
//开启offset自动提交
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"false");
2.1:同步提交
while (true){
//获取数据
ConsumerRecords<String, String> poll = consumer.poll(100);
//解析数据
for (ConsumerRecord<String, String> record : poll) {
String key = record.key();
String value = record.value();
System.out.println("key==>"+key+" value==>"+value);
}
//offset同步提交,当前线程会阻塞直到 offset 提交成功,才会再次拉取数据
consumer.commitSync();
}
2.2:异步提交
while (true){
//获取数据
ConsumerRecords<String, String> poll = consumer.poll(100);
//解析数据
for (ConsumerRecord<String, String> record : poll) {
String key = record.key();
String value = record.value();
System.out.println("key==>"+key+" value==>"+value);
}
//offset异步提交
consumer.commitAsync(new OffsetCommitCallback() {
@Override
public void onComplete(Map<TopicPartition,
OffsetAndMetadata> offsets, Exception exception) {
if (exception != null) {
System.err.println("Commit failed for" +
offsets);
}
}
});
}
2.4:数据漏消费和重复消费分析
无论是同步提交还是异步提交 offset
,都有可能会造成数据的漏消费或者重复消费。先
提交
offset
后消费,有可能造成数据的漏消费;而先消费后提交
offset
,有可能会造成数据
的重复消费。
2.5:自定义存储offset
offset 的维护是相当繁琐的,因为需要考虑到消费者的 Rebalace。 当有新的消费者加入消费者组、已有的消费者推出消费者组或者所订阅的主题的分区发 生变化,就会触发到分区的重新分配,重新分配的过程叫做 Rebalance。 消费者发生 Rebalance 之后,每个消费者消费的分区就会发生变化。因此消费者要首先获取到自己被重新分配到的分区,并且定位到每个分区最近提交的 offset 位置继续消费。
package com.yrl.comsumer;
import org.apache.kafka.clients.consumer.ConsumerRebalanceListener;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import java.util.*;
public class MyConsumerOffset {
private static Map<TopicPartition, Long> currentOffset = new
HashMap<>();
public static void main(String[] args) {
Properties props = new Properties();
props.put("bootstrap.servers", "hadoop112:9092");
props.put("group.id", "test");
props.put("enable.auto.commit", "false");
props.put("key.deserializer",
"org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer",
"org.apache.kafka.common.serialization.StringDeserializer");
//创建一个消费者
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
//消费者订阅主题
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) {
consumer.seek(partition, getOffset(partition));//定位到最近提交的 offset 位置继续消费
}
}
});
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);//消费者拉取数据
for (ConsumerRecord<String, String> record : records) {
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) {
}
}