Kafka的API

Kafka的API

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

Producer API

消息发送流程

Kafka的Producer发送消息采用异步发送的方式。在消息发送的过程中,涉及到了两个线程——main线程Sender线程,以及一个线程共享变量——RecordAccumulator。 main线程将消息发送给RecordAccumulator,Sender线程不断从RecordAccumulator中拉取消息发送到Kafka broker。

 

 

相关参数:

batch.size: 只有数据积累到batch.size之后,sender才会发送数据。

linger.ms: 如果数据迟迟未达到batch.size,sender等待linger.time之后就会发送数据。

  • 编写代码

需要用到的类:

KafkaProducer:需要创建一个生产者对象,用来发送数据

ProducerConfig:获取所需的一系列配置参数

ProducerRecord:每条数据都要封装成一个ProducerRecord对象

最普通朴素的,不带回调函数的Producer,异步发送

package com.newbie.kafka.clients;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

/**
 * @author yhj_newbie
 * @create 2022-02-11 17:18
 * @description
 */
public class JavaProducer {
    public static void main(String[] args) {
        Properties properties = new Properties();
        //指定broker的地址
        properties.put("bootstrap.servers", "Linux01:9092,Linux02:9092,Linux03:9092");
        //指定写入数据key的序列化方式
        properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        //指定写入数据value的序列化方式
        properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);

        //封装的发送数据
        ProducerRecord<String, String> record1 = new ProducerRecord<String, String>("hellokafka", "hello kafka1");

        kafkaProducer.send(record1);//不会立即发送,而是缓存在客户端中

        ProducerRecord<String, String> record2 = new ProducerRecord<String, String>("hellokafka", "hello kafka2");

        kafkaProducer.send(record2);

        System.out.println(6666);

        //最好在调用close之前,调用一次flush
        kafkaProducer.flush();

        //将kafkaProducer在close之前,会将缓存的数据flush到broker中
        kafkaProducer.close();
    }
}

 注意:

  • kafkaProducer.send(),不会立即发送数据,而是缓存在客户端中。直到kafkaProducer.flush()或kafkaProducer.close()

Puducer的一些额外参数

    //以下.setProperty,用.put替代亦可

	//配置ACKS:数据同步应答
    props.setProperty("acks", "1")
    //配置重试次数
    props.setProperty("retries", "100")
    //设置压缩方式(不在kafka,而在客户端先压缩)
    props.setProperty("compression", "snappy")
    //设置客户端数据缓存的大小(从默认的32M改为100M)
    props.setProperty("buffer.memory", 100 * 1024 * 1024 + "") //不能直接引数字,要的不是这串数字而是结果
    //设置请求超时时间
    props.setProperty("request.timeout.ms", 45000 + "")
    //批次大小
    props.put("batch.size", 16384);
	//等待时间
	props.put("linger.ms", 1);

 带回调函数的自动提交Offset的Producer,异步发送

package com.newbie.kafka.clients;

import org.apache.kafka.clients.producer.*;

import java.util.Properties;
import java.util.concurrent.ExecutionException;

/**
 * @author yhj_newbie
 * @create 2022-02-12 19:16
 * @description
 */
public class test {
    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();
    }
}
  • 回调函数会在producer收到ack时调用,为异步调用, 该方法有两个参数,分别是 RecordMetadata和Exception,如果Exception为null,说明消息发送成功,如果 Exception不为null,说明消息发送失败。

  • 消息发送失败会自动重试,不需要我们在回调函数中手动重试。

Consumer API

Consumer消费数据时的可靠性是很容易保证的,因为数据在Kafka中是持久化的,不用担心数据丢失问题。

由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置的继续消费,所以consumer需要实时记录自己消费到了哪个offset,以便故障恢复后继续消费,offset的维护是Consumer消费数据时必须考虑的问题。

编写代码

需要用到的类:

KafkaConsumer: 需要创建一个消费者对象,用来消费数据

ConsumerConfig: 获取所需的一系列配置参数

ConsuemrRecord: 每条数据都要封装成一个ConsumerRecord对象

为了使我们能够专注于自己的业务逻辑,Kafka提供了自动提交offset的功能。自动提交offset的相关参数:

enable.auto.commit: 是否开启自动提交offset功能

auto.commit.interval.ms: 自动提交offset的时间间隔

自动提交offset

最普通朴素的,自动提交offset的Consumer,长期监听版本,异步发送

package com.newbie.kafka.clients;

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.serialization.StringDeserializer;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

import java.util.Arrays;

/**
 * @author yhj_newbie
 * @create 2022-02-11 17:18
 * @description
 */
public class JavaConsumer {
    public static void main(String[] args) {
        Properties properties = new Properties();
        //指定kafka broker的地址
        properties.setProperty("bootstrap.servers", "Linux01:9092,Linux02:9092,Linux03:9092");
        //指定写入数据key的反序列化方式
        properties.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        //指定写入数据value的反序列化方式
        properties.setProperty("value.deserializer", StringDeserializer.class.getName());
        //指定消费者组id(必须的)
        properties.setProperty("group.id", "g0001");
        //指定从什么位置开始读取数据,默认是启动之后latest读数据
        properties.setProperty("auto.offset.reset", "earliest");

        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);

        //消费可以监听一到多个topic
//        List<String> topics = Arrays.asList("test", "wordcount");
        List<String> topics = Arrays.asList("hellokafka");

        kafkaConsumer.subscribe(topics);
//        //以下程序读完就退出了,我们希望持续监听数据
//        ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(5));
//
//        for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
//            System.out.println(consumerRecord);
//        }

        while (true) {
            //poll拉取数据等待的时长,五秒没数据就下一次拉取,有数据则拉取一个批次(有大小限制)
            ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(5));
            //一次会拉取一个批次(0到多条数据)
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.println(consumerRecord);
            }
            System.out.println("88888888");
        }
    }
}
  • properties.setProperty("auto.offset.reset","earliest");

    • earliest:从头开始 相当于shell中的 --from-beginning,如果记录了偏移量(旧消费者组),则从偏移量开始读,新消费者组则从头读

    • latest:从消费者启动之后

    • 默认是latest

  • List<String> topics = Arrays.asList("hellokafka"); 这里可以订阅不止一个topic

  • kafkaConsumer.poll(Duration.ofSeconds(5));

    • 5秒内没数据拉取下一个批次,一个批次有0-n条数据

手动提交offset

props.setProperty("enable.auto.commit", "false")

虽然自动提交offset十分简介便利,但由于其是基于时间提交的, 开发人员难以把握 offset提交的时机。因此Kafka还提供了手动提交offset的API。

手动提交offset的方法有两种:分别是commitSync(同步提交)commitAsync(异步提交)

  • 相同点:都会将本次poll的一批数据最高的偏移量提交;

  • 不同点:commitSync阻塞当前线程,一直到提交成功,并且会自动失败重试(由不可控因素导致,也会出现提交失败);而commitAsync新开一个单独线程,没有失败重试机制,故有可能提交失败。

普通手动提交offset的Consumer

package cn._51doit.kafka.clients

import java.time.Duration
import java.util
import java.util.Properties

import org.apache.kafka.clients.consumer.{ConsumerRecords, KafkaConsumer}
import org.apache.kafka.common.serialization.StringDeserializer

/**
 * 消费者不自动提交偏移量,手动提交偏移量
 *
 */
object ConsumerCommitOffsetDemo {

  def main(args: Array[String]): Unit = {

    // 1 配置参数
    val props = new Properties()
    //从哪些broker消费数据
    props.setProperty("bootstrap.servers", "node-1.51doit.cn:9092,node-2.51doit.cn:9092,node-3.51doit.cn:9092")
    // 反序列化的参数
    props.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer")
    props.setProperty("value.deserializer", classOf[StringDeserializer].getName)
    // 指定group.id
    props.setProperty("group.id", "g004")

    // 指定消费的offset从哪里开始
    //earliest:从头开始 --from-beginning
    //latest:从消费者启动之后
    props.setProperty("auto.offset.reset", "earliest") //[latest, earliest, none]

    // 是否自动提交偏移量  offset
    props.setProperty("enable.auto.commit", "false") // kafka自动维护偏移量     手动维护偏移量

    //enable.auto.commit   5000

    // 2 消费者的实例对象
    val consumer: KafkaConsumer[String, String] = new KafkaConsumer[String, String](props)

    // 订阅   参数类型  java的集合
    val topic: util.List[String] = java.util.Arrays.asList("test")

    // 3 订阅主题
    consumer.subscribe(topic)

    while (true) {
      // 4  拉取数据
      val msgs: ConsumerRecords[String, String] = consumer.poll(Duration.ofMillis(2000))

      //新的方式
      import scala.collection.JavaConverters._
      for (cr <- msgs.asScala) {
        println(cr)
      }
      if (!msgs.isEmpty) {//改进:有新的数据才提交偏移量
        //提交偏移量
        //异步提交偏移量,会启动一个单独的线程完成提交偏移量,主程序可以进行执行循环
        consumer.commitAsync()
        //同步提交,必须写入到kafka的特殊的topic中的偏移量成功后,才会进行下一次循环
        //consumer.commitSync()
      }
    }

    //consumer.close()

  }
}

 

带回调函数的Consumer1(主体与上方代码块相同)

//在提交后,用回调函数判断成功与否执行另外一些操作

while (true) {
      // 4  拉取数据
      val msgs: ConsumerRecords[String, String] = consumer.poll(Duration.ofMillis(2000))

      //新的方式
      import scala.collection.JavaConverters._
      for (cr <- msgs.asScala) {
        println(cr)
      }
      if (!msgs.isEmpty) {
        //提交偏移量
        consumer.commitAsync(new OffsetCommitCallback {
          override def onComplete(map: util.Map[TopicPartition, OffsetAndMetadata], e: Exception): Unit = {
            //提交成功后可以做一些操作
            if (e == null) {
              println("偏移量提交成功")
            } else if (e.isInstanceOf[CommitFailedException]) {
              println("提交偏移量失败")
            } else {
              println("其他异常")
            }
          }
        })
      }
    }

 

带回调函数的Consumer2(主体与上方代码块相同)

// 提交指定的偏移量到Kafka特殊的topic中,这里以取最大偏移量为例
// 偏移量由:group.id, topic, partition -> offset
    
	while (true) {
      // 4  拉取数据
      val msgs: ConsumerRecords[String, String] = consumer.poll(Duration.ofMillis(2000))

      //新的方式
      import scala.collection.JavaConverters._
      val consumerRecords: Iterable[ConsumerRecord[String, String]] =  msgs.asScala

      for (cr <- consumerRecords) {
        println(cr)
      }

      if (!msgs.isEmpty) {

        //(("test", 1), 21)
        //(("test", 1), 22)
        //(("test", 2), 15)
        //(("test", 2), 13)
        //(("test", 2), 14)
        //(("test", 0), 17)
        val offsets: Iterable[((String, Int), Long)] = consumerRecords.map(record => ((record.topic(), record.partition()), record.offset()))
        //获取每个topic、每个分区,最大的偏移量
        val maxOffsetInPartition: Iterable[((String, Int), Long)] = offsets.groupBy(_._1).map{
          case(_, v) => {
            v.maxBy(_._2)
          }
        }
        //将数据整理成 : Map[TopicPartition, OffsetAndMetadata]
        val offsetMap: Map[TopicPartition, OffsetAndMetadata] = maxOffsetInPartition.map(t => {
          (new TopicPartition(t._1._1, t._1._2), new OffsetAndMetadata(t._2, null))
        }).toMap
        println(offsetMap.toBuffer)
        //提交偏移量
        consumer.commitSync(offsetMap.asJava)

      }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值