如何发送时间窗口KTable的最终kafka-streams聚合结果?

时间:2016-08-13 18:48:38

标签: apache-kafka apache-kafka-streams

我想做的是:

  1. 使用数字主题(长&#39)消费记录
  2. 聚合(计算)每个5秒窗口的值
  3. 将FINAL汇总结果发送给另一个主题
  4. 我的代码如下所示:

    KStream<String, Long> longs = builder.stream(
            Serdes.String(), Serdes.Long(), "longs");
    
    // In one ktable, count by key, on a five second tumbling window.
    KTable<Windowed<String>, Long> longCounts = 
            longs.countByKey(TimeWindows.of("longCounts", 5000L));
    
    // Finally, sink to the long-avgs topic.
    longCounts.toStream((wk, v) -> wk.key())
            .to("long-counts");
    

    看起来一切都按预期工作,但聚合会发送到每个传入记录的目标主题。我的问题是如何只发送每个窗口的最终聚合结果?

3 个答案:

答案 0 :(得分:19)

在Kafka Streams中,没有“最终聚合”这样的东西。 Windows始终处于打开状态以处理迟到的记录(当然窗口不会永久保存,它们会被丢弃直到它们的保留时间到期 - 但是,当窗口被丢弃时没有特殊操作。)

有关详细信息,请参阅Confluent文档:http://docs.confluent.io/current/streams/

因此,对于聚合的每次更新,都会生成结果记录(因为Kafka Streams还会更新迟到记录的聚合结果)。您的“最终结果”将是最新的结果记录(在窗口被丢弃之前)。根据您的使用情况,手动重复数据删除可以解决问题(使用低杠杆API,transform()process()

此博客文章也可能有所帮助:https://timothyrenner.github.io/engineering/2016/08/11/kafka-streams-not-looking-at-facebook.html

另一篇不使用标点符号解决此问题的博文:http://blog.inovatrend.com/2018/03/making-of-message-gateway-with-kafka.html

<强>更新

使用KIP-328,添加KTable#suppress()运算符,允许以严格的方式抑制连续更新,并为每个窗口发出单个结果记录;权衡是增加延迟。

答案 1 :(得分:1)

从Kafka Streams 2.1版开始,您可以实现此using suppress

上面提到的apache Kafka Streams文档中有一个示例,当用户在一小时内发生少于三个事件时会发送警报:

KGroupedStream<UserId, Event> grouped = ...;
grouped
  .windowedBy(TimeWindows.of(Duration.ofHours(1)).grace(ofMinutes(10)))
  .count()
  .suppress(Suppressed.untilWindowCloses(unbounded()))
  .filter((windowedUserId, count) -> count < 3)
  .toStream()
  .foreach((windowedUserId, count) -> sendAlert(windowedUserId.window(), windowedUserId.key(), count));

this答案的更新中所述,您应该意识到这种权衡。此外,抑制()的note是基于事件时间的。

答案 2 :(得分:0)

我遇到了这个问题,但是我解决了这个问题,在固定窗口之后并使用抑制的API添加了grace(0)

public void process(KStream<SensorKeyDTO, SensorDataDTO> stream) {

        buildAggregateMetricsBySensor(stream)
                .to(outputTopic, Produced.with(String(), new SensorAggregateMetricsSerde()));

    }

private KStream<String, SensorAggregateMetricsDTO> buildAggregateMetricsBySensor(KStream<SensorKeyDTO, SensorDataDTO> stream) {
        return stream
                .map((key, val) -> new KeyValue<>(val.getId(), val))
                .groupByKey(Grouped.with(String(), new SensorDataSerde()))
                .windowedBy(TimeWindows.of(Duration.ofMinutes(WINDOW_SIZE_IN_MINUTES)).grace(Duration.ofMillis(0)))
                .aggregate(SensorAggregateMetricsDTO::new,
                        (String k, SensorDataDTO v, SensorAggregateMetricsDTO va) -> aggregateData(v, va),
                        buildWindowPersistentStore())
                .suppress(Suppressed.untilWindowCloses(unbounded()))
                .toStream()
                .map((key, value) -> KeyValue.pair(key.key(), value));
    }


    private Materialized<String, SensorAggregateMetricsDTO, WindowStore<Bytes, byte[]>> buildWindowPersistentStore() {
        return Materialized
                .<String, SensorAggregateMetricsDTO, WindowStore<Bytes, byte[]>>as(WINDOW_STORE_NAME)
                .withKeySerde(String())
                .withValueSerde(new SensorAggregateMetricsSerde());
    }

在这里您可以看到结果

enter image description here