Kafka API

kafka 版本 2.1.0

1 Producer API

1.1 原生

引入maven文件

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

KafkaProducer<K,V> 是Kafka集群发送消息的客户端
KafkaProducer是线程安全的,多个线程共享一个KafkaProducer实例将会比多个线程更快
下面是一个示例

Properties props = new Properties();
 props.put("bootstrap.servers", "localhost:9092");
 props.put("acks", "all");
 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>("my-topic", Integer.toString(i), Integer.toString(i)));

 producer.close();

KafkaProducer是由一个Buffer空间和一个I/O线程组成。其中,Buffer保存尚未传输到服务器的消息,I/O线程负责将这些消息转换为请求并将他们传输到集群。使用后,如果关闭资源失败会导致资源泄漏
send() 方法是一步的。调用时,它将record添加到buffer中国呢并立即返回。这允许producer批量高效发送record
ack配置控制用于请求完成的条件。示例中指定了All
如果请求失败了,producer可以自动重试,除非我们设置重试次数为0。启用重试也会导致重复消费的问题

Producer为每个分区未发送的record维护了不同的buffer。buffer大小由batch.size配置。设置增大可以导致更多的批处理,但是需要更多的内存
默认情况下,即使未发送的的record未达到batch.size大小,我们也会发送。由配置linger.ms设置。这表示producer在发送请求之前等待的毫秒数,以希望同一批次可以发送更多的record

buffer.memory控制producer可用于缓冲的内存总量。如果记录的发送速度超过了将记录发送到服务器的速度,则该缓冲区空间将被耗尽。当缓冲区空间用尽时,其他发送调用将阻塞。阻止时间由max.block.ms配置
key.serializer和value.serializer 是ProducerRecord键值序列化的方法

生产者分为幂等生产者和事务生产者
要启用幂等,必须将enable.idempotence配置设置为true。如果设置,则重试配置将默认为Integer.MAX_VALUE,而acks配置将默认为全部。幂等生产者没有API更改,因此无需修改现有应用程序即可利用此功能。

要利用幂等生成器,必须避免重新发送应用程序级别,因为这些应用程序无法重复删除。因此,如果应用程序启用幂等性,建议将重试配置保留为未设置状态,因为它将默认为Integer.MAX_VALUE。此外,如果send(ProducerRecord)即使无限次重试也返回错误(例如,如果消息在发送之前已在缓冲区中过期),则建议关闭生产者并检查最后产生的消息的内容,以确保不能重复。最后,生产者只能保证在单个会话中发送的消息具有幂等性。要使用事务产生器和附带的API,必须设置transactional.id配置属性。如果设置了transactional.id,则会自动启用幂等性以及幂等性所依赖的生产者配置。此外,交易中包含的主题应配置为具有持久性。特别是,replication.factor应该至少为3,并且这些主题的min.insync.replicas应该设置为2。最后,为了从端到端实现事务保证,必须使使用者配置为也仅读取已提交的消息。transactional.id的目的是启用单个生产者实例的多个会话之间的事务恢复。它通常从分区的有状态应用程序中的分片标识符派生。因此,它对于在分区应用程序中运行的每个生产者实例应该是唯一的。所有新的事务性API都将被阻止,并且将在失败时引发异常。下面的示例说明了如何使用新的API。除了所有100条消息都是单个事务的一部分之外,它与上面的示例相似。

Properties props = new Properties();
 props.put("bootstrap.servers", "localhost:9092");
 props.put("transactional.id", "my-transactional-id");
 Producer<String, String> producer = new KafkaProducer<>(props, new StringSerializer(), new StringSerializer());

 producer.initTransactions();

 try {
     producer.beginTransaction();
     for (int i = 0; i < 100; i++)
         producer.send(new ProducerRecord<>("my-topic", Integer.toString(i), Integer.toString(i)));
     producer.commitTransaction();
 } catch (ProducerFencedException | OutOfOrderSequenceException | AuthorizationException e) {
     // We can't recover from these exceptions, so our only option is to close the producer and exit.
     producer.close();
 } catch (KafkaException e) {
     // For all other exceptions, just abort the transaction and try again.
     producer.abortTransaction();
 }
 producer.close();

2 Consumer API

引入Maven

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

KafkaConsumer 处理Kafka broker节点的估值,应对集群内topic分区的调整 。使用consumer group处理消费负责。
consumer维持一个和broker节点的tcp连接。consumer不是线程安全的。
Offset:偏移量
消费者Group和订阅

Consumer Group 提供了消费者的伸缩性和容错性。
Group中成员通过分区策略消费某几个分区。Topic中一个分区只能分配给一个消费者

检测消费者存活Alive

订阅Topiic 后,消费者将会加入group poll数据。如果consumer在session.timeout.ms期间崩溃或无法发送heatbeaters,则该consumer 被视为已死,其负责的分区将被重新分配

示例
自动提交

Properties props = new Properties();
        props.put("bootstrap.servers", "localhost: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("hetao_test"));
        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());
            }
        }

bootstrap.servers: 指定kafka节点代理获取连接
enable.auto.commit: 自动提交 配合auto.commit.interval.ms. 使用

手动提交

Properties props = new Properties();
        props.put("bootstrap.servers", "localhost: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> kafkaConsumer = new KafkaConsumer<String, String>(props);
        kafkaConsumer.subscribe(Arrays.asList("hetao_test"));
        final int minBatchSize = 200;
        List<ConsumerRecord<String, String>> buffer = new ArrayList<>();
        while (true){
            ConsumerRecords<String, String> records = kafkaConsumer.poll(100);
            for (ConsumerRecord<String, String> record : records) {
                buffer.add(record);
            }

            if(buffer.size() >= minBatchSize){
                insertIntoDb(buffer);
                kafkaConsumer.commitSync();
                buffer.clear();
            }
        }

在示例中,我们将一批记录批处理放到内存中,到一定量时,批量插入到数据库。
如果是自动提交的话,在将记录poll后,就已经提交,但是此时插入数据库有可能失败
为了避免这种情况,我们仅在将相应的记录插入数据库后才手动提交offset,这样可以精确控制消费的offset
但是,在插入数据库之后,但在提交之前的间隔内也可能失败(几毫米),在这种情况下,需要重复消费。

上面的示例使用commitSync将所有接收到的记录标记为已提交。在某些情况下,您可能希望通过显示指定偏移量来更好地控制已提交的记录。

try {
         while(running) {
             ConsumerRecords<String, String> records = consumer.poll(Long.MAX_VALUE);
             for (TopicPartition partition : records.partitions()) {
                 List<ConsumerRecord<String, String>> partitionRecords = records.records(partition);
                 for (ConsumerRecord<String, String> record : partitionRecords) {
                     System.out.println(record.offset() + ": " + record.value());
                 }
                 long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset();
                 consumer.commitSync(Collections.singletonMap(partition, new OffsetAndMetadata(lastOffset + 1)));
             }
         }
     } finally {
       consumer.close();
     }

注意:提交的offset应该始终是下一条消息的offset。因此,在调用commitSync(offsets)时,应在处理的消息的偏移量上添加应该

手动设定订阅的分区
  String topic = "foo";
     TopicPartition partition0 = new TopicPartition(topic, 0);
     TopicPartition partition1 = new TopicPartition(topic, 1);
     consumer.assign(Arrays.asList(partition0, partition1));
外置存储offset
控制消费者的position

Kafka允许使用seek(TopicPartition,long)指定位置以指定新位置。还可以使用特殊方法来查找服务器维护的最早和最新偏移(分别为seekToBeginning(Collection)和seekToEnd(Collection))。

消费流量控制

Kafka通过使用pause(Collection)和resume(Collection)在指定的分配分区上暂停消耗并在未来的poll(Duration)调用中分别在指定的已暂停分区上恢复消耗,来支持消耗流的动态控制。

Read 原子事务消息

事务是在Kafka 0.11.0中引入的,其中应用程序可以原子地写入多个主题和分区。为了使它起作用,应该将从这些分区读取的使用者配置为仅读取已提交的数据。这可以通过在使用者的配置中设置Isolation.level = read_committed来实现。

多线程

Kafka Consumer不是线程安全。所有网络I / O都发生在进行调用的应用程序线程中。需要自己保证同步。否则会抛出异常ConcurrentModificationException

该规则的唯一例外是wakeup(),可以从外部线程安全地使用它来中断活动操作。在这种情况下,将从该操作的线程阻塞中抛出WakeupException。这可用于从另一个线程关闭使用者。以下代码段显示了典型模式:

public class KafkaConsumerRunner implements Runnable {
     private final AtomicBoolean closed = new AtomicBoolean(false);
     private final KafkaConsumer consumer;

     public void run() {
         try {
             consumer.subscribe(Arrays.asList("topic"));
             while (!closed.get()) {
                 ConsumerRecords records = consumer.poll(Duration.ofMillis(10000));
                 // Handle new records
             }
         } catch (WakeupException e) {
             // Ignore exception if closing
             if (!closed.get()) throw e;
         } finally {
             consumer.close();
         }
     }

     // Shutdown hook which can be called from a separate thread
     public void shutdown() {
         closed.set(true);
         consumer.wakeup();
     }
 }

然后,在单独的线程中,可以通过设置已关闭标志并唤醒使用者来关闭使用者。

  closed.set(true);
  consumer.wakeup();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值