Kafka Streams是一个Java库,用于在Apache Kafka之上开发流处理应用程序。 这是有关Kafka Streams及其API的一系列博客文章中的第一篇。
这不是有关Kafka Stream的“理论指南”(尽管我以前已经介绍过其中的某些方面)
在这一部分,我们将介绍无状态作业在Kafka Streams DSL API中-具体来说,KStream如过滤,地图,通过...分组 etc. The DSL API in Kafka Streams offers a powerful,functional style programming model to define stream processing topologies. Please note that the 桌子API还提供了无状态功能,本文所涵盖的内容也将适用于这种情况(或多或少)
The APIs (
KStream
etc.) referenced in this post can be found in the Kafka Streams javadocs
The setup
首先,您需要创建一个KafkaStreams实例。 它需要一个拓扑结构和相关配置(以java.util.Properties)
为您的Kafka流应用程序设置所需的配置:
Properties config = new Properties();
config.setProperty(StreamsConfig.APPLICATION_ID_CONFIG, App.APP_ID);
config.setProperty(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
config.setProperty(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName());
config.setProperty(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass().getName());
然后我们可以建立一个拓扑结构定义处理管道(此博客文章的其余部分将重点介绍拓扑的无状态部分)
您可以创建KafkaStreams实例并开始处理
KafkaStreams app = new KafkaStreams(topology, config);
app.start();
new CountdownLatch(1).await(); // wait forever
Stateless operations using KStream
我通常喜欢将事物归类到存储桶中-帮助我“分而治之”。 在这种情况下,我通过将各种KStream操作成过滤,地图等等
让我们开始吧!
filter
您可以使用过滤根据标准省略或包括记录。 例如,如果发送给主题的值包含一个单词,并且您想包含大于指定长度的单词。 您可以使用谓语并将其传递给过滤方法-这将创建一个新的KStream instance with the 过滤ed records
KStream<String, String> stream = builder.stream("words");
stream.filter(new Predicate<String, String>() {
@Override
public boolean test(String k, String v) {
return v.length() > 5;
}
})
也可以使用filterNot如果你想排除根据条件进行记录。 这是一个lambda样式示例:
KStream<String, String> stream = builder.stream("words");
stream.filterNot((key,value) -> value.startsWith("foo"));
map
常用的无状态操作是地图。 对于Kafka Streams,可用于转换输入中的每个记录KStream by applying a 地图per function
这有多种口味-地图,地图Values,flatMap,flatMapValues
只需使用地图如果要同时更改键和值,则使用此方法。 例如,将键和值转换为大写
stream.map(new KeyValueMapper<String, String, KeyValue<String, String>>() {
@Override
public KeyValue<String, String> apply(String k, String v) {
return new KeyValue<>(k.toUpperCase(), v.toUpperCase());
}
});
使用mapValues如果您要更改的只是值:
stream.mapValues(value -> value.toUpperCase());
flatMap与map类似,但是它允许您返回多个记录(核心价值s)
stream.flatMap(new KeyValueMapper<String, String, Iterable<? extends KeyValue<? extends String, ? extends String>>>() {
@Override
public Iterable<? extends KeyValue<? extends String, ? extends String>> apply(String k, String csv) {
String[] values = csv.split(",");
return Arrays.asList(values)
.stream()
.map(value -> new KeyValue<>(k, value))
.collect(Collectors.toList());
}
})
在上面的示例中,流中的每个记录获取flatMapped,以便首先将每个CSV(逗号分隔)值拆分为各部分,核心价值将为CSV字符串的每个部分创建一对。 例如 如果您有这些记录(富 <-> a,b,c)和(酒吧 <-> d,e)(哪里富和酒吧是键),则结果流将具有五个条目-(富,a),(富,b),(富,c),(酒吧,d),(酒吧,e)
使用flatMapValues如果您只想从流中接受一个值并返回值的集合
group
如果要对内容进行有状态的聚合KStream,您首先需要按其键将其记录分组以创建一个KGroupedStream。
我们将涵盖KGroupedStream在本系列的后续博客文章中
这是一个如何使用groupByKey
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> stream = builder.stream(INPUT_TOPIC);
KGroupedStream<String,String> kgs = stream.groupByKey();
的广义版本通过...分组Key是通过...分组这使您能够使用KeyValueMapper
stream.groupBy(new KeyValueMapper<String, String, String>() {
@Override
public String apply(String k, String v) {
return k.toUpperCase();
}
});
在两种情况下(通过...分组Key和通过...分组),如果您需要使用其他塞尔德(序列化器和解串器)而不是默认版本,请使用接受一个分组宾语
stream.groupByKey(Grouped.with(Serdes.Bytes(), Serdes.Long()));
Terminal operations
Kafka Streams中的终端操作是一种返回的方法虚空而不是另一个KStream要么桌子。
您可以使用至 method 至 s至re the records of a KStream 至 a 至pic in Kafka.
KStream<String, String> stream = builder.stream("words");
stream.mapValues(value -> value.toUpperCase())
.to("uppercase-words");
的重载版本至 allows you 至 specify a 产生的 object 至 cus至mize the 塞德斯和隔板
stream.mapValues(value -> value.toUpperCase())
.to("output-topic",Produced.with(Serdes.Bytes(), Serdes.Long()));
除了指定静态主题名称,您还可以使用TopicNameExtractor并包含任何自定义逻辑以动态方式选择特定主题
stream.mapValues(value -> value.toUpperCase())
.to(new TopicNameExtractor<String, String>() {
@Override
public String extract(String k, String v, RecordContext rc) {
return rc.topic()+"_uppercase";
}
});
在这个例子中,我们利用RecordContext其中包含记录的元数据,以获取主题并附加_大写对此
在上述所有情况下,接收器主题应预先存在于Kafka中
如果您想登录KStream记录(出于调试目的),请使用打印方法。 它接受一个实例印制配置行为。
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> stream = builder.stream(INPUT_TOPIC);
stream.mapValues(v -> v.toUpperCase()).print(Printed.toSysOut());
这将打印出记录,例如 如果你通过(foo,bar)和(约翰·杜) to the input topic, they will get converted to uppercase和logged as such:
[KSTREAM-MAPVALUES-0000000001]: foo, BAR
[KSTREAM-MAPVALUES-0000000001]: john, DOE
您也可以使用打印到文件(代替toSysOut)定位到特定文件
前言方法类似于打印和窥视即
- 这也是一个终端操作(例如打印)它接受一个Foreach动作(喜欢窥视)
Miscellaneous operations
以来打印方法是终端操作,您可以选择使用窥视返回相同KStream实例! 它接受一个Foreach动作可以用来指定您要对每条记录执行的操作,例如 记录键和值
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> stream = builder.stream(INPUT_TOPIC);
stream.mapValues(v -> v.toUpperCase())
.peek((k,v) -> System.out.println("key="+k+", value="+v))
.to(OUTPUT_TOPIC);
在上面的示例中,您将能够看到正在记录的键和值,它们还将具体化为输出主题(与打印操作)
科是我没有使用过的一种方法(说实话!),但是看起来很有趣。 它使您能够评估记录中的每条记录KStream针对多个条件(以谓语)并输出多个(数组)KStreams。 The key here is that you can use multiple 谓语s instead of a single one as is the case with 过滤和过滤Not。
您可以合并二KStream一起成为一个。
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> stream1 = builder.stream("topic1");
KStream<String, String> stream2 = builder.stream("topic2");
stream1.merge(stream2).to("output-topic");
请注意,结果流可能没有按顺序排列所有记录
如果要为您的每个记录派生一个新键(也可以具有不同的类型)KStream, 使用selectKey接受一个的方法核心价值Mapper。selectKey类似于地图但不同的是地图将返回类型限制为核心价值宾语
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> stream = builder.stream(INPUT_TOPIC);
stream.selectKey(new KeyValueMapper<String, String, String>() {
@Override
public String apply(String k, String v) {
return k.toUpperCase();
}
})
在使用Kafka Streams DSL开发处理管道时,您会发现自己使用以下方法将结果流记录推送到输出主题至 and then creating a new stream from that (output) 至pic i.e.
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> stream1 = builder.stream(INPUT_TOPIC);
stream1.mapValues(v -> v.toUpperCase()).to(OUTPUT_TOPIC);
//output topic now becomes the input source
KStream<String, String> stream2 = builder.stream(OUTPUT_TOPIC);
//continue processing with stream2
stream2.filter((k,v) -> v.length > 5).to(LENGTHY_WORDS_TOPIC);
可以使用通过方法。 因此,您可以按以下方式重写上面的内容:
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> stream = builder.stream(INPUT_TOPIC);
stream.mapValues(v -> v.toUpperCase())
.through(OUTPUT_TOPIC)
.filter((k,v) -> v.length > 5)
.to(LENGTHY_WORDS_TOPIC);
在这里,我们将记录(具有大写值)具体化为一个中间主题,然后继续处理(使用过滤在这种情况下),最后将过滤后的结果存储在另一个主题中
现在就这样。 请继续关注本系列中即将发布的帖子!
References
请不要忘记查看以下有关Kafka Streams的资源