【Kafka】Kafka 1.0.1案例详解之消息发布订阅

    在本章内容中我们主要讲解大家在生产系统中最常见,最基础的发布订阅功能,也就是Producer API和Consumer API,然后在下一章节中我们将深入讲解Kafka的Streams API、Connect API和AdminClient API。我们先来看下Kafka的五种核心API: 

Kafka核心API

Kafka包括五种核心的API:

  1. Producer API: 通过Producer API,我们可以向Kafka集群中的topic发送消息流。

  2. Consumer API:  通过Consumer API,我们可以从Kafka集群中的topic拉取对应的实时数据流。

  3. Streams API: 通过该API我们可以将拉取某一个或多个topic的流数据作为输入进行转换并输出到另一个或多个topic中。

  4. Connect API: 通过该API我们可以实现一个Connector来将数据源源不断地从一些数据源抽取到Kafka,或者将Kafka中的数据推送给其他接收系统,或者应用程序。

  5. AdminClient API: 通过该API我们可以管理,或者观察Topic、Broker或者其他的Kafka对象。

Producer API

    KafkaProducer实现了Producer接口,它是线程安全的。在多线程环境下,共享同一个producer将会比使用producer实例更加高效。

接下来我们来演示两个例子,一个是普通的Kafka发送样例、另一个是从0.11开始支持发送的事务型消息。

首先我们需要引入maven的jar包:

maven引用方式

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

某些情况下,还需要引入slf4j-api包:

<dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>1.7.25</version>
</dependency>

1.通用的发送代码

一般情况下,我们直接实例化一个producer,发送消息:

Properties props = new Properties();
props.put("bootstrap.servers", "192.168.0.181:9092");
props.put("acks", "all");
props.put("retries", 0);
props.put("batch.size", 16384);
props.put("linger.ms", 1);
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<>("my-topic", Integer.toString(i), Integer.toString(i)));

producer.close();

2.支持事务的发送代码

实现逻辑比较简单,这里不再赘述。直接上代码:

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

Consumer API

    最早之前Kafka API分为高阶API和低阶API,在当前的版本1.0.1中,Kafka已经将原来手动提交offset的方式封装的很好了,我们只需要通过commitSync或者commitAsync方法提交即可。接下来我们将演示下,这两种提交方式如何实现。

1.自动offset提交方式实现

Properties props = new Properties();
props.put("bootstrap.servers", "192.168.0.181:9092,192.168.0.182:9092,192.168.0.183: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(Collections.singletonList("my-topic"));
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.手动offset提交方式实现

  • commitSync

Properties props = new Properties();
props.put("bootstrap.servers", "192.168.0.181:9092,192.168.0.182:9092,192.168.0.183: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(Collections.singletonList("my-topic"));
final int minBatchSize = 200;
List<ConsumerRecord<String, String>> buffer = new ArrayList<>();
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
   for (ConsumerRecord<String, String> record : records) {
buffer.add(record);
   }
if (buffer.size() >= minBatchSize) {
buffer.forEach(record -> {
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
       });

       consumer.commitSync();
       buffer.clear();
   }
}

commitSync方式我们还可以通过传入对应的partition和offset信息来精确的提交某一个partition对应的offset。需要注意的是,我们提交的时候,必须指定当前消费的message的offset加1,也就是:lastOffset + 1,下面的案例我们演示如何实现:

try {
while(true) {
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();
}

3.消费指定的partition

除了通过上述方法消费kafka消息之外,我们还可以更加有针对性的,通过assign(Collection)来消费某一组TopicPartition:

String topic = "my-topic";
TopicPartition partition0 = new TopicPartition(topic, 0);
TopicPartition partition1 = new TopicPartition(topic, 1);
consumer.assign(Arrays.asList(partition0, partition1));

通过以上代码指定TopicPartition之后,其他的实现方式与上面的示例一致。但是遗憾的是,我们无法动态变更对应的partitin。

4.将offset提交到外部系统存储

在有些情况下,我们需要把offset存储到数据库中方便进行分析和监控,那么我们可以通过以下三个步骤来完成

  • Configure enable.auto.commit=false

  • Use the offset provided with each ConsumerRecord to save your position.

  • On restart restore the position of the consumer using seek(TopicPartition, long).

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值