我正在使用Kafka流来计算使用跳跃时间窗口在过去3分钟内发生的事件数量:
[Test]
[TestCase(SomeEnum.SomeValue)]
public void MethodName_Condition_ThrowsException(SomeEnum someValue)
{
//...
}
当运行使用者并将某些消息推送到输入主题时,随着时间的推移,它会收到以下更新:
public class ViewCountAggregator {
void buildStream(KStreamBuilder builder) {
final Serde<String> stringSerde = Serdes.String();
final Serde<Long> longSerde = Serdes.Long();
KStream<String, String> views = builder.stream(stringSerde, stringSerde, "streams-view-count-input");
KStream<String, Long> viewCount = views
.groupBy((key, value) -> value)
.count(TimeWindows.of(TimeUnit.MINUTES.toMillis(3)).advanceBy(TimeUnit.MINUTES.toMillis(1)))
.toStream()
.map((key, value) -> new KeyValue<>(key.key(), value));
viewCount.to(stringSerde, longSerde, "streams-view-count-output");
}
public static void main(String[] args) throws Exception {
// some not so important initialization code
...
}
}
这几乎是正确的,但它永远不会收到更新:
single 1
single 1
single 1
five 1
five 4
five 5
five 4
five 1
如果没有它,我的消费者更新计数器将永远不会在没有更长时间的事件时将其设置为零。我期待消费的消息看起来像这样:
single 0
five 0
我是否缺少一些可以帮助我实现此类行为的配置选项/参数?
答案 0 :(得分:6)
这几乎是正确的,但它永远不会收到更新:
首先,计算出的输出 正确。
其次,为什么它是正确的:
如果应用窗口化聚合,则仅创建具有实际内容的窗口(我熟悉的所有其他系统将生成相同的输出)。因此,如果对于某些键,没有比窗口大小更长的时间段的数据,则没有实例化窗口,因此也没有计数。
如果没有内容,不实例化窗口的原因很简单:处理器无法知道所有密钥。在您的示例中,您有两个键,但稍后可能会出现第三个键。您希望从一开始就获得<thirdKey,0>
吗?此外,由于数据流本质上是无限的,因此密钥可能会消失并且永远不会再出现。如果你记得所有看过的键,并且如果没有关键消失的数据就会发出<key,0>
,你会永远发出<key,0>
吗?
我不想说你的预期结果/语义没有意义。它只是您的一个非常具体的用例,并不适用于一般情况。因此,流处理器不会实现它。
第三:你能做什么?有多种选择:
map
步骤并保留键的Windowed<K>
类型,以便使用者获取信息到哪个窗口记录属于)#transform()
步骤,其执行与(1)中所述相同的操作。为此,注册标点回调可能会有所帮助。方法(2)应该更容易跟踪密钥,因为您可以将状态存储附加到转换步骤,因此不需要处理下游消费者的状态(和故障/恢复)。 / p>
然而,这两种方法的棘手部分仍然是何时缺少一个密钥,即,等到产生<key,0>
之前等待多长时间。请注意,数据可能迟到(也就是乱序),即使您确实发出了<key,0>
,迟到的记录也可能会在代码之后生成<key,1>
消息确实发出<key,0>
条记录。但也许这对你的情况来说并不是一个问题,因为你似乎只能使用最新的窗口。
最后但并非最不重要的一条评论:您似乎只使用最新计数,而较新的窗口会覆盖下游消费者中的旧窗口。因此,可能值得探索&#34;交互式查询&#34;直接进入count
运营商的状态,而不是消费主题并更新其他状态。这可能会让您重新设计并简化下游应用程序。查看docs以及关于Interactive Queries的非常好的博文,了解更多详情。