Apache Kafka 入门 - Kafka API 简单用法
2017-06-27 20:03
423 查看
Apache Kafka 入门
Kafka的基本配置和运行Kafka命令详细介绍
Kafka-manager的基本配置和运行
Kafka API 简单用法
Spring Boot 集成Kafka
本篇为第四篇。
第四篇和第五篇源码下载:
链接:http://pan.baidu.com/s/1dE4vpBj 密码:j74j
Kafka API 简单用法
本篇会用到以下依赖:<dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-clients</artifactId> <version>0.10.2.0</version> </dependency> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka-streams</artifactId> <version>0.10.2.0</version> </dependency>
生产者API
参考官方文档中 KafkaProducer 的介绍。Kafka客户端用于向 Kafka 集群发布记录。生产者是线程安全的,跨线程共享一个生产者实例通常比拥有多个实例要快。这是一个简单的例子,使用生产者发送包含序列号的字符串作为键/值对的记录,代码如下。
package com.github.abel533.kafka.api; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; import java.util.Properties; public class ProducerApi { public static void main(String[] args) { Properties props = new Properties(); props.put("bootstrap.servers", "192.168.16.150: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<String, String>( "t1", Integer.toString(i), Integer.toString(i))); } producer.close(); } }
生产者包括一个缓冲区池,它保存尚未发送到服务器的记录,以及一个后台I/O线程,负责将这些记录转换为请求并将其传输到集群。使用后未能关闭生产者将泄漏这些资源。
该
send()方法是异步的。当被调用时,它将记录添加到待处理记录发送的缓冲区并立即返回。这允许生产者将各个记录收集在一起以获得效率。
acks配置其请求被视为完整性的标准。
"all"意味着领导者将等待完整的同步副本来确认记录。只要至少有一个同步复制品仍然存在,这将保证记录不会丢失。这是最强大的保证。这相当于设置acks = -1。
如果请求失败,生产者可以自动重试,但是由于我们指定
retries为
0,所以不会重试。启用重试还会产生重复的可能性(有关详细信息,请参阅有关消息传递语义的文档 )。
生产者维护每个分区的未发送出去的缓冲区。这些缓冲区的大小由
batch.size指定。使此更大可以缓存更多,但需要更多的内存(因为我们通常会为每个活动分区提供缓冲区)。
默认情况下,即使缓冲区中存在额外的未使用空间,缓冲区也可立即发送。但是,如果要减少请求数可以设置
linger.ms为大于0 的毫秒数。这将指示生产者在发送请求之前等待该毫秒数,这样将有更多记录到达缓冲区。这类似于Nagle在TCP中的算法。例如,在上面的代码片段中,可能所有100条记录都将在单个请求中发送,因为我们将延迟时间设置为1毫秒。但是,如果我们没有填满缓冲区,则此设置会为我们的请求增加1毫秒的延迟,以便等待更多记录到达。在重负荷下 ,即使
linger.ms=0,在时间上紧接在一起的记录也将一起批量处理。将其设置为大于0的值可能会让请求更少和更高效,而不是在最大负载下以少量延迟为代价。
buffer.memory控制生产者可用于缓冲的总内存量。如果记录的发送速度比可以传输到服务器的速度快,那么这个缓冲空间就会耗尽。当缓冲区空间耗尽时,附加的发送呼叫将被阻塞。
max.block.ms决定阻塞时间的阈值,超出此时间时,会引发
TimeoutException。
key.serializer和
value.serializer指导如何将用户提供的
ProducerRecord的键和值转换成字节。您可以使用提供的
ByteArraySerializer或
StringSerializer用于简单的字符串或字节类型。
该客户端可以与0.10.0版本或更高版本的broker进行通信。旧的或较新的broker可能不支持某些功能。当调用运行的broker程序版本不可用的API时,会产生
UnsupportedVersionException异常。
有关生产者更多的配置属性可以参考Producer Configs。
消费者API
参考官方文档中的 KafkaConsumer 介绍。从Kafka集群中消费记录的客户端。
这个客户端透明地处理卡夫卡经纪人的失败,并透明地适应作为在集群中迁移的主题分区。该客户端还与代理商进行交互,以允许消费群体使用消费者群体来负载平衡消费。
消费者保持TCP连接到必要的经纪人以获取数据。使用后未能关闭消费者将泄漏这些连接。消费者不是线程安全的。有关详细信息,请参阅多线程处理。
自动提交偏移
下面这个例子使用了自动提交,设定了每1000ms提交一次偏移(就是当前已读取消息的位置)。package com.github.abel533.kafka.api; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; import java.util.Arrays; import java.util.Properties; public class ConsumerAOC { public static void main(String[] args) { final Properties props = new Properties(); props.put("bootstrap.servers", "192.168.16.150: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("t1")); while (true) { ConsumerRecords<String, String> records = consumer.poll(1000); for (ConsumerRecord<String, String> record : records) System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value()); } } }
首先通过
bootstrap.servers设置要连接的Broker,多个可以使用逗号隔开。通过
group.id设置了当前的分组id,同一个分组id中的多个消费者可以通过负载均衡处理消息(消费者数量多于主题的分区数时,多出来的消费者不会被分配任何消息)。
通过设置
enable.auto.commit为
true开启自动提交,自动提交的频率由
auto.commit.interval.ms设置。
后面两个
deserializer用于序列化 key 和 value。
通过
consumer.subscribe定义了主题
t1,一个消费者可以订阅多个主题。通过
consumer.poll获取消息,参数1000(毫秒)的含义是,当缓冲区中没有可用消息时,以此时间进行轮训等待。当设置为0时,理解返回当前可用的消息或者返回空。
手动提交偏移
消费者不是必须自动提交偏移。用户也可以手动控制提交偏移来决定消息是否已被消费。当消息需要经过一些特殊逻辑进行处理时,手动提交就非常有必要,没有经过处理的消息不应该当成已消费。package com.github.abel533.kafka.api; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Properties; public class ConsumerManual { public static void main(String[] args) { Properties props = new Properties(); props.put("bootstrap.servers", "192.168.16.150: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(Arrays.asList("t1", "t2")); 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) { //逻辑处理,例如保存到数据库 consumer.commitSync(); buffer.clear(); } } } }
在这个例子中,我们将
enable.auto.commit设置为
false,这是因为这个值默认情况下是
true,只有手动设置为
false后才能进行手动提交。
每当buffer的大小超过设置的批量大小后就会通过
consumer.commitSync()进行提交。
在某些情况下,您可能希望通过明确指定偏移量来更精确地控制已经提交的记录。在下面的例子中,我们在完成处理每个分区中的记录之后提交偏移量。
package com.github.abel533.kafka.api; 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.clients.consumer.OffsetAndMetadata; import org.apache.kafka.common.TopicPartition; import java.util.*; public class ConsumerManualPartition { public static void main(String[] args) { Properties props = new Properties(); props.put("bootstrap.servers", "192.168.16.150:9092"); props.put("group.id", "test2"); 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("t1")); 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(partition.partition() + ": " + record.offset() + ": " + record.value()); } long lastOffset = partitionRecords.get( partitionRecords.size() - 1).offset(); consumer.commitSync( Collections.singletonMap(partition, new OffsetAndMetadata(lastOffset + 1))); } } } finally { consumer.close(); } } }
因为每个主题可能存在多个分区,每个分区都维护了一个索引,因此上面针对不同的分区进行处理。
消费者API中还存在很多有用的方法,可以通过查看官方的API文档了解更多。
流API
参考官方文档中 KafkaStreams 的介绍。流API允许对来自一个或多个主题的消息进行连续计算,并将结果发送到零个,一个或多个主题中。
可以从Kafka获取某个主题的消息,经过处理后输出到另一个主题。相当于是对主题做了一个加工。下面是一个示例,这个示例从t1主题获取消息,然后计算数字的平方后发送消息到t2主题中。
package com.github.abel533.kafka.api; import org.apache.kafka.common.serialization.Serdes; import org.apache.kafka.streams.KafkaStreams; import org.apache.kafka.streams.StreamsConfig; import org.apache.kafka.streams.kstream.KStreamBuilder; import java.util.HashMap; import java.util.Map; public class StreamApi { public static void main(String[] args) { Map<String, Object> props = new HashMap<>(); props.put(StreamsConfig.APPLICATION_ID_CONFIG, "my-stream-processing-application"); props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.16.150:9092"); props.put(StreamsConfig.KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass()); props.put(StreamsConfig.VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass()); StreamsConfig config = new StreamsConfig(props); KStreamBuilder builder = new KStreamBuilder(); builder.stream("t1").mapValues(value -> { Integer i = Integer.parseInt((String)value); return String.valueOf(i * i); }).to("t2"); KafkaStreams streams = new KafkaStreams(builder, config); streams.start(); } }
在例子中
StreamsConfig.APPLICATION_ID_CONFIG用于设置当前流处理的ID,具有相同流ID的应用会根据输入主题的分区来分配任务。当流处理应用的数量大于主题的分区数时,超出部分的流处理不会被分配任何消息。
以上是关于Kafka基本API的应用。
相关文章推荐
- Apache Kafka 入门 - Kafka API 简单用法
- 大数据生态系统基础:Apache Kafka基础(四):最新kafka编程入门:Stream API
- API入门系列之四 -一个相当简单的SDK程序
- API入门系列之四 -一个相当简单的SDK程序
- 报表引擎API开发入门—简单程序数据集
- 漫游Kafka入门篇之简单介绍(1)
- SpringJDBC+SpringMVC+MySQL的简单入门级用法
- VB Api简单入门(3)-Api声明
- Java Json API:Gson使用简单入门
- Kafka使用入门教程 简单介绍
- 漫游Kafka入门篇之简单介绍
- JFreeChart简单介绍及用法(入门)
- Map的简单用法入门
- 漫游Kafka入门篇之简单介绍
- OWL API简单入门与使用
- (转)漫游Kafka入门篇之简单介绍
- API入门系列之四 -相当简单的SDK程序
- android google maps 简单入门之申请Android Map API Key
- MKNetworkKit框架入门及简单用法
- spring整合mybatis(入门级简单教程5)--使用mybatis的API