Kafka Streams API博客系列的第一部分介绍了无状态的诸如过滤
,地图
等等。在这一部分,我们将探索有状态的Kafka Streams DSL API中的操作。 它着重于聚合操作,例如骨料
,计数
,减少
以及相关概念的讨论。
Aggregation
聚合操作应用于相同键的记录。 Kafka Streams支持以下聚合-骨料
,计数
,减少
。 如先前的博客所述,分组是聚合的先决条件。 你可以跑通过。..分组
(或其变体)在KStream
或一个桌子
导致KGroupedStream
和分组表分别
。
桌子
分组之前不在无状态操作博客中介绍
骨料
的骨料
功能有两个关键组成部分-初始化器
和聚合器
。 收到第一条记录后,初始化器
被调用,并被用作聚合器
。 对于后续记录,聚合器将当前记录与计算的聚合(直到现在)一起使用来进行计算。 从概念上讲,这是对无限数据集执行的有状态计算-这是有状态的,因为计算当前状态会考虑当前状态(键值记录)以及最新状态(当前聚合)。 这可以用于诸如移动平均,总和,计数等方案。
这是一个如何计算计数的示例,即接收特定密钥的次数
code examples are available on GitHub
StreamsBuilder builder = new StreamsBuilder();
KStream<String,String> stream = builder.stream(INPUT_TOPIC);
桌子<String,Count> 骨料 = stream.通过...分组Key()
.aggregate(new Initializer<Count>() {
@Override
public Count apply() {
return new Count("",0);
}
}, new 聚合器<String, String, Count>() {
@Override
public Count apply(String k, String v, Count aggKeyCount) {
Integer currentCount = aggKeyCount.getCount();
return new Count(k, currentCount + 1);
}
});
aggregate.toStream()
.map((k,v) -> new KeyValue<>(k, v.getCount()))
.to(COUNTS_TOPIC, Produced.with(Serdes.String(), Serdes.Integer()));
计数
count
是一种通常使用的聚合形式,它是作为一流操作提供的。 将流记录按键分组后(KGroupedStream
),您可以使用此操作计算特定密钥的记录数。
的aggregate
可以用一个方法调用来代替做事方式!
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> stream = builder.stream(INPUT_TOPIC);
stream.通过...分组Key().count();
减少
您可以使用减少
合并价值流。 的aggregate
前面介绍的操作是的广义形式reduce
。 您可以实现以下功能和
, 分
, 最大值
等。这是一个例子最大值
StreamsBuilder builder = new StreamsBuilder();
KStream<String, Long> stream = builder.stream(INPUT_TOPIC, Consumed.with(Serdes.String(), Serdes.Long()));
stream.groupByKey()
.reduce(new Reducer<Long>() {
@Override
public Long apply(Long currentMax, Long v) {
Long max = (currentMax > v) ? currentMax : v;
return max;
}
}).toStream().to(OUTPUT_TOPIC);
返回builder.build();
请注意,所有聚合操作都会忽略带有
空值
密钥是显而易见的,因为这些功能集的目的是对特定密钥的记录进行操作
Aggregation和state stores
在以上示例中,汇总值被推送到输出主题-尽管这不是强制性的。 可以将聚合结果存储在本地状态存储中。 这是一个例子:
StreamsBuilder builder = new StreamsBuilder();
KStream<String, String> stream = builder.stream(INPUT_TOPIC);
stream.groupByKey().count(Materialized.as("计数店"));
在上面的示例中,count
还创建一个名为的本地状态存储count-store
然后可以使用交互式查询自省。
这些状态存储可以在内存中,也可以使用岩石数据库
。 这允许可扩展性
由于每个状态存储都位于特定的Kafka Streams应用程序本地,该应用程序处理主题的不同分区的输入-因此,总体状态分布在(潜在的)应用程序的多个实例中(除非全局表
s)。 另一个关键属性是高可用性
因为这些状态存储的内容已备份为Kafka变更日志
又名压实的
提供高可用性的主题(尽管可以禁用)-如果应用程序实例崩溃,则可以从Kafka本身还原状态存储内容
分组表
一种分组表
当groupBy
*操作在桌子
。 就像KGroupedStream
, 有一个分组表
是对一个集合应用聚合的先决条件桌子
。aggregate
, count
和reduce
以相同的方式工作KGroupedTable
就像他们使用KGrou`pedStream一样。 但是,有一个重要的区别需要强调。
一种KTable
在概念上与KStream
从某种意义上说,它表示某个时间点的数据快照(非常类似于数据库表)。 它是一个可变的实体,而不是KStream
它代表一个不变的+无限的记录序列。 考虑到这一差异,aggregate
and reduce
在一个功能KGroupedTable
还添加一个额外的Aggregator
(通常称为减法器),并且在更新密钥或空值
获得值。
加窗
有状态的Kafka Streams操作也支持窗口ing
。 这使您可以将流处理管道的范围限定为特定的时间窗口/范围,例如 跟踪号 每分钟链接点击数或否。 每小时的唯一身份浏览量
去表演Window
ed聚合一组记录,则必须创建一个KGroupedStream
(如上所述)使用groupBy
在一个KStream
然后使用窗口化
操作(有两种重载形式)。 您可以在传统窗口(滚动,跳动或滑动)或基于会话的时间窗口之间进行选择
使用windowedBy(Windows<W> windows)
在一个KGroupedStream
返回一个TimeWindowedKStream
您还可以在其上调用上述聚合操作。 例如 如果您希望在特定时间范围内(例如5分钟)点击次数,请选择滚动时间窗口。 这样可以确保在给定的时间范围内清楚地分隔记录,即从user1的上午10点至10:05 AM的点击将分别进行汇总(计数),并且新的时间段(窗口)将从10:06 AM开始,在此期间 点击计数器重置为零并再次计数
`
StreamsBuilder builder = new StreamsBuilder();
KStream stream = builder.stream(INPUT_TOPIC);
TimeWindowedKStream windoweded = stream.groupByKey()。windowedBy(TimeWindows.of(Duration.ofMinutes(5)));
windowed.count()。toStream()。to(OUTPUT_TOPIC);
`
其他窗口类型包括:
-
翻滚
永远不会重叠的时间窗口,即一条记录只会是一个窗口的一部分... - ...与
跃迁
可以在一个或多个时间范围/窗口中显示记录的时间窗口 -
滑行
时间窗口适合与加盟操作一起使用
有状态操作的另一种类型是
Joining
。 这是一个广泛的话题,其本身值得一整篇文章(或其他系列文章?)
如果您要考虑“会话”,即活动时间间隔为已定义的不活动间隔,请使用windowedBy(SessionWindows窗口)
返回一个SessionWindowedKStream
.
`
StreamsBuilder builder = new StreamsBuilder();
KStream stream = builder.stream(INPUT_TOPIC);
stream.groupByKey().windowedBy(SessionWindows.with(Duration.ofMinutes(5)))
.toStream().to(OUTPUT_TOPIC);
return builder.build();
`
这就是Kafka Streams博客系列的全部内容。 请继续关注下一部分,它将演示如何使用内置的测试实用程序来测试Kafka Streams应用程序。
References
请不要忘记查看以下有关Kafka Streams的资源