四、Kafka API
2、 Consumer API
- Consumer 消费数据时的可靠性是很容易保证的,因为数据在 Kafka 中是持久化的,故不用担心数据丢失问题。
- 由于 consumer 在消费过程中可能会出现断电宕机等故障,consumer 恢复后,需要从故障前的位置的继续消费,所以 consumer 需要实时记录自己消费到了哪个offset,以便故障恢复后继续消费。
- 所以offset的维护是 Consumer 消费数据是必须考虑的问题。
2.1 自动提交offset
1)编写代码
- 需要用到的类:
- KafkaConsumer:需要创建一个消费者对象,用来消费数据
- ConsumerConfig:获取所需的一系列配置参数
- ConsuemrRecord:每条数据都要封装成一个 ConsumerRecord 对象
- 为了使我们能够专注于自己的业务逻辑,Kafka 提供了自动提交offset的功能。
- 自动提交 offset 的相关参数:
- enable.auto.commit:是否开启自动提交offset功能
- auto.commit.interval.ms:自动提交offset的时间间隔
2)消费者自动提交offset
- 编写 MyConsumer
public class MyConsumer {
public static void main(String[] args) {
//todo 创建一个配置对象
Properties props = new Properties();
//1.去配置集群
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop105:9092");
//2.指定消费者的组
props.put(ConsumerConfig.GROUP_ID_CONFIG, "bajie");
//3.指定自动提交偏移量
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
//4.每次自动提交偏移量的间隔时间
props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");
//5.设定key和value的反序列化
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
//todo 创建一个消费者对象
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
//对于消费者需要订阅主题
ArrayList<String> topics = new ArrayList<>();
//topics.add("wukong");
topics.add("bajie");
consumer.subscribe(topics);
while (true){
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> record : records) {
System.out.println("topic:"+record.topic()+
"--partition:"+record.partition()+
"--offset:"+record.offset()+
"--key:"+record.key()+
"--value:"+record.value());
}
}
}
}
启动消费者,再启动生产者
生产者:
topic:bajie--partition:0--offset:44
topic:bajie--partition:0--offset:45
topic:bajie--partition:0--offset:46
topic:bajie--partition:0--offset:47
topic:bajie--partition:0--offset:48
topic:bajie--partition:0--offset:49
topic:bajie--partition:0--offset:50
topic:bajie--partition:0--offset:51
topic:bajie--partition:0--offset:52
topic:bajie--partition:0--offset:53
消费者:
topic:bajie--partition:0--offset:44--key:null--value:er shi xiong shi fu bei yao guai zhua zou le---0
topic:bajie--partition:0--offset:45--key:null--value:er shi xiong shi fu bei yao guai zhua zou le---1
topic:bajie--partition:0--offset:46--key:null--value:er shi xiong shi fu bei yao guai zhua zou le---2
topic:bajie--partition:0--offset:47--key:null--value:er shi xiong shi fu bei yao guai zhua zou le---3
topic:bajie--partition:0--offset:48--key:null--value:er shi xiong shi fu bei yao guai zhua zou le---4
topic:bajie--partition:0--offset:49--key:null--value:er shi xiong shi fu bei yao guai zhua zou le---5
topic:bajie--partition:0--offset:50--key:null--value:er shi xiong shi fu bei yao guai zhua zou le---6
topic:bajie--partition:0--offset:51--key:null--value:er shi xiong shi fu bei yao guai zhua zou le---7
topic:bajie--partition:0--offset:52--key:null--value:er shi xiong shi fu bei yao guai zhua zou le---8
topic:bajie--partition:0--offset:53--key:null--value:er shi xiong shi fu bei yao guai zhua zou le---9
注意:关闭消费者后,生产生产数据后,再启动消费者,消费者可以消费到数据(偏移量)启动消费者后先会看看是否有偏移量存在,有就可以消费
2.2 重置Offset
- 使用重置offset的前提:
- 当Kafka中没有初始偏移量或者服务器上的当前偏移量不再存在
auto.offset.rest = earliest | latest | none |
编写ResetOffsetConsumer
public class ResetOffsetConsumer {
public static void main(String[] args) {
//todo 创建一个配置对象
Properties props = new Properties();
//1.去配置集群
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop105:9092");
//2.指定消费者的组
props.put(ConsumerConfig.GROUP_ID_CONFIG, "test02");
/**
* auto.offset.reset: What to do when there is no initial offset in
* Kafka or if the current offset does not exist any more on the server
* (e.g. because that data has been deleted):
* earliest: automatically reset the offset to the earliest offset
* latest: automatically reset the offset to the latest offset
* 当你想要重头开始消费 你需要满足3个条件
* 1.你当前的消费者的消费者组从来没有消费过订阅的主题
* 2.你当前的消费者的消费者组的offset已经找不到(过期了超过7天对应数据被删除了)
* 3.你需要将AUTO_OFFSET_RESET_CONFIG置位earliest
*/
//3.重置offset
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
//4.指定自动提交偏移量
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
//5.每次自动提交偏移量的间隔时间
props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");
//6.设定key和value的反序列化
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
//todo 创建一个消费者对象
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
//对于消费者需要订阅主题
ArrayList<String> topics = new ArrayList<>();
topics.add("bajie");
topics.add("wukong");
consumer.subscribe(topics);
while (true){
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> record : records) {
System.out.println("topic:"+record.topic()+
"--partition:"+record.partition()+
"--offset:"+record.offset()+
"--key:"+record.key()+
"--value:"+record.value());
}
}
}
}
- 执行结果和–from-beginning 一样,能消费到之前所有的数据
2.3手动提交 offset
- 虽然自动提交offset十分简介便利,但由于其是基于时间提交的,开发人员难以把握offset提交的时机。因此 Kafka还提供了手动提交offset的API。
- 手动提交offset的方法有两种:分别是commitSync(同步提交)和commitAsync(异步提交)。两者的相同点是,都会将本次poll的一批数据最高的偏移量提交;不同点是,commitSync 阻塞当前线程,一直到提交成功,并且会自动失败重试(由不可控因素导致,也会出现提交失败);而 commitAsync则没有失败重试机制,故有可能提交失败。
1)同步提交 offset
- 由于同步提交 offset 有失败重试机制,故更加可靠,以下为同步提交offset的示例。
public class CustomComsumer {
public static void main(String[] args) {
Properties props = new Properties();
//Kafka集群
props.put("bootstrap.servers", "hadoop105:9092");
//消费者组,只要group.id相同,就属于同一个消费者组
props.put("group.id", "test");
props.put("enable.auto.commit", "false");//关闭自动提交offset
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("bajie"));//消费者订阅主题
while (true) {
//消费者拉取数据
ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
for (ConsumerRecord<String, String> record : records) {
System.out.println(record.topic()+" -- "+
record.partition()+" -- "+
record.offset()+" -- "+
record.key()+" -- "+
record.value());
}
//同步提交,当前线程会阻塞直到offset提交成功
consumer.commitSync();
}
}
}
2)异步提交offset
- 虽然同步提交offset更可靠一些,但是由于其会阻塞当前线程,直到提交成功。因此吞吐量会收到很大的影响。因此更多的情况下,会选用异步提交offset的方式。
以下为异步提交offset的示例:
public class CustomConsumer {
public static void main(String[] args) {
Properties props = new Properties();
//Kafka集群
props.put("bootstrap.servers", "hadoop105:9092");
//消费者组,只要group.id相同,就属于同一个消费者组
props.put("group.id", "test");
//关闭自动提交offset
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("bajie"));//消费者订阅主题
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);//消费者拉取数据
for (ConsumerRecord<String, String> record : records) {
System.out.println(record.topic()+" -- "+
record.partition()+" -- "+
record.offset()+" -- "+
record.key()+" -- "+
record.value());
}
//异步提交
consumer.commitAsync(new OffsetCommitCallback() {
@Override
public void onComplete(Map<TopicPartition, OffsetAndMetadata> offsets, Exception exception) {
if (exception != null) {
System.err.println("Commit failed for" + offsets);
}
}
});
}
}
}
3) 数据漏消费和重复消费分析
- 无论是同步提交还是异步提交offset,都有可能会造成数据的漏消费或者重复消费。先提交offset后消费,有可能造成数据的漏消费;而先消费后提交offset,有可能会造成数据的重复消费。
注意:消费者偏移量存储是记录下一次从哪消费,大小是当前消费数加1