【Kafka三】Kakfa API

1.Producer API

1.1 消息发送流程

Kafka 的 Producer 发送消息采用的是异步发送的方式。在消息发送的过程中,涉及到了两个线程——main 线程和 Sender 线程,以及一个线程共享变量——RecordAccumulator。main 线程将消息发送给 RecordAccumulator,Sender 线程不断从RecordAccumulator 中拉取消息发送到 Kafka broker。
在这里插入图片描述
相关参数:
batch.size:只有数据积累到 batch.size 之后,sender 才会发送数据。
linger.ms:如果数据迟迟未达到 batch.size,sender 等待 linger.time 之后就会发送数据。

1.2 生产者代码

1.3 依赖

<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>0.11.0.0</version>
</dependency>

1.2 需要用到的类

KafkaProducer:需要创建一个生产者对象,用来发送数据
ProducerConfig:获取所需的一系列配置参数
ProducerRecord:每条数据都要封装成一个 ProducerRecord 对象

1.3 不带回调函数的 API

public class CustomProducer {
 public static void main(String[] args) throws ExecutionException, InterruptedException {
 Properties props = new Properties();
 //kafka 集群,broker-list
 props.put("bootstrap.servers", "hadoop102:9092");
 props.put("acks", "all");
 //重试次数
 props.put("retries", 1);
 //批次大小
 props.put("batch.size", 16384);
 //等待时间
 props.put("linger.ms", 1);
 //RecordAccumulator 缓冲区大小
 props.put("buffer.memory", 33554432);
 props.put("key.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
 props.put("value.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
 Producer<String, String> producer = new KafkaProducer<>(props);
 for (int i = 0; i < 100; i++) {
  producer.send(new ProducerRecord<String, String>("first", Integer.toString(i), Integer.toString(i)));
 }
 producer.close();
 }

1.4 带回调函数的 API

回调函数会在 producer 收到 ack 时调用,为异步调用,该方法有两个参数,分别是RecordMetadata 和 Exception,如果 Exception 为 null,说明消息发送成功,如果Exception 不为 null,说明消息发送失败。
注意:消息发送失败会自动重试,不需要我们在回调函数中手动重试。

public class CustomProducer {
public static void main(String[] args) throws ExecutionException,InterruptedException {
Properties props = new Properties();
 props.put("bootstrap.servers", "hadoop102:9092");//kafka 集群,broker-list
 props.put("acks", "all");
 props.put("retries", 1);//重试次数
 props.put("batch.size", 16384);//批次大小
 props.put("linger.ms", 1);//等待时间
 props.put("buffer.memory", 33554432);//RecordAccumulator 缓冲区大小
 props.put("key.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
 props.put("value.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
 Producer<String, String> producer = new KafkaProducer<>(props);
 for (int i = 0; i < 100; i++) {
 producer.send(new ProducerRecord<String, String>("first", Integer.toString(i), Integer.toString(i)), new Callback() {
 //回调函数,该方法会在 Producer 收到 ack 时调用,为异步调用
 @Override
 public void onCompletion(RecordMetadata metadata,Exception exception) {
 if (exception == null) {
 System.out.println("success->" +
 metadata.offset());
 } else {
 exception.printStackTrace();
 }
 }
 });
 }
 producer.close();
 }
}

2. Consumer API

Consumer 消费数据时的可靠性是很容易保证的,因为数据在 Kafka 中是持久化的,故不用担心数据丢失问题。
由于 consumer 在消费过程中可能会出现断电宕机等故障,consumer 恢复后,需要从故障前的位置的继续消费,所以 consumer 需要实时记录自己消费到了哪个 offset,以便故障恢复后继续消费。所以 offset 的维护是 Consumer 消费数据是必须考虑的问题。

2.1 需要用到的类

KafkaConsumer:需要创建一个消费者对象,用来消费数据
ConsumerConfig:获取所需的一系列配置参数
ConsuemrRecord:每条数据都要封装成一个 ConsumerRecord 对象

自动提交 offset 的相关参数
enable.auto.commit:是否开启自动提交 offset 功能
auto.commit.interval.ms:自动提交 offset 的时间间隔

2.2 代码

public class CustomConsumer {
public static void main(String[] args) {
 Properties props = new Properties();
 props.put("bootstrap.servers", "hadoop102:9092");
 props.put("group.id", "test");
 props.put("enable.auto.commit", "true");
 props.put("auto.commit.interval.ms", "1000");
 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"));
 while (true) {
 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());
 }
 }
}

2.3 手动提交 offset

虽然自动提交 offset 十分简介便利,但由于其是基于时间提交的,开发人员难以把握offset 提交的时机。因此 Kafka 还提供了手动提交 offset 的 API。
手动提交 offset 的方法有两种:分别是 commitSync(同步提交)和 commitAsync(异步提交)。两者的相同点是,都会将本次 poll 的一批数据最高的偏移量提交;不同点是,commitSync 阻塞当前线程,一直到提交成功,并且会自动失败重试(由不可控因素导致,也会出现提交失败);而 commitAsync 则没有失败重试机制,故有可能提交失败。

自动提交的问题(容易丢失数据/重复消费)
自动提交的时候,处理过程中就已经把offset提交了,但是还没处理完,服务还是挂了,由于offset 已经提交,这时候会从下一个消息开始消费。如果因为这个把自动提交时间延长至10s,如果消费者5s处理完业务,这时候挂掉了,由于offset没有提交(没到10s),服务起来的时候继续从上一个消息进行消费,上一个消息就消费重复。

无论是同步提交还是异步提交 offset,都有可能会造成数据的漏消费或者重复消费。先提交 offset 后消费,有可能造成数据的漏消费;而先消费后提交 offset,有可能会造成数据的重复消费。

2.4 自定义存储 offset

Kafka 0.9 版本之前,offset 存储在 zookeeper,0.9 版本及之后,默认将 offset 存储在 Kafka的一个内置的 topic 中。除此之外,Kafka 还可以选择自定义存储 offset。
offset 的维护是相当繁琐的,因为需要考虑到消费者的 Rebalace。
当有新的消费者加入消费者组、已有的消费者退出消费者组或者所订阅的主题的分区发生变化,就会触发到分区的重新分配,重新分配的过程叫做 Rebalance。
消费者发生 Rebalance 之后,每个消费者消费的分区就会发生变化。因此消费者要首先获取到自己被重新分配到的分区,并且定位到每个分区最近提交的 offset 位置继续消费。
要实现自定义存储 offset,需要借助 ConsumerRebalanceListener,以下为示例代码,其中提交和获取 offset 的方法,需要根据所选的 offset 存储系统自行实现。

public class CustomConsumer {
 private static Map<TopicPartition, Long> currentOffset = new
HashMap<>();
public static void main(String[] args) {
//创建配置信息
 Properties props = new Properties();
//Kafka 集群
 props.put("bootstrap.servers", "hadoop102:9092");
//消费者组,只要 group.id 相同,就属于同一个消费者组
 props.put("group.id", "test");
//关闭自动提交 offset
 props.put("enable.auto.commit", "false");
 //Key 和 Value 的反序列化类
 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);//提交当前offset(自己实现)
 }
 //该方法会在 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) {
 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) {
 	//提交到mysql(表设计,基础列 消费者组/topic/分区/offset)
 }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值