KafkaStreams:获取窗口最终结果

时间:2019-01-09 12:29:08

标签: kotlin apache-kafka apache-kafka-streams

通过抑制中间结果,是否有可能在Kafka Streams中获得window final result

我无法实现这个目标。我的代码有什么问题?

    val builder = StreamsBuilder()
    builder.stream<String,Double>(inputTopic)
            .groupByKey()
            .windowedBy(TimeWindows.of(Duration.ofSeconds(15)))
            .count()
            .suppress(Suppressed.untilWindowCloses(unbounded())) // not working)
            .toStream()
            .print(Printed.toSysOut())

它导致此错误:

Failed to flush state store KSTREAM-AGGREGATE-STATE-STORE-0000000001: 
java.lang.ClassCastException: org.apache.kafka.streams.kstream.Windowed cannot be cast to java.lang.String

代码/错误详细信息:https://gist.github.com/robie2011/1caa4772b60b5a6f993e6f98e792a380

3 个答案:

答案 0 :(得分:9)

问题在于,在窗口期间Streams自动包装显式Serdes,但不自动包装默认Serde的方式上,这是一个令人困惑的不对称性。恕我直言,这是一个应予纠正的疏忽,所以我提出了:https://issues.apache.org/jira/browse/KAFKA-7806

正如其他人所指出的,解决方案是显式地将密钥Serde设置为上游,而不依赖于默认密钥Serde。您可以:

使用Materialized

在窗口聚合上设置Serdes
val builder = StreamsBuilder()
builder.stream<String,Double>(inputTopic)
        .groupByKey()
        .windowedBy(TimeWindows.of(Duration.ofSeconds(15)))
        .count(Materialized.with(Serdes.String(), Serdes.Long()))
        .suppress(Suppressed.untilWindowCloses(unbounded())))
        .toStream()
        .print(Printed.toSysOut())

(如Nishu推荐)

(请注意,count操作的命名不必 ,这具有使其可查询的副作用)

或将Serdes设置在更上游,例如在输入上:

val builder = StreamsBuilder()
builder.stream<String,Double>(inputTopic, Consumed.with(Serdes.String(), Serdes.Double()))
        .groupByKey()
        .windowedBy(TimeWindows.of(Duration.ofSeconds(15)))
        .count()
        .suppress(Suppressed.untilWindowCloses(unbounded())))
        .toStream()
        .print(Printed.toSysOut())

(按照wardziniak的建议)

选择是您的;我认为在这种情况下,两种情况都没有太大区别。如果您要进行的聚合与count不同,那么您可能还是要通过Materialized来设置值serde,所以前者可能是更统一的样式。

我还注意到您的窗口定义没有设置宽限期。窗口关闭时间定义为window end + grace period,默认值为24小时,因此,直到应用程序运行了24小时的数据后,您才能看到抑制产生的任何东西。

为了您的测试努力,我建议您尝试:

.windowedBy(TimeWindows.of(Duration.ofSeconds(15)).grace(Duration.ZERO))

在生产环境中,您需要选择一个宽限期,以平衡流中期望的事件延迟量与希望从抑制中看到的排放提示量之间的平衡。

最后一点,我注意到您没有更改默认的缓存或提交间隔。结果,您会注意到count运算符本身将在默认情况下缓冲更新30秒钟,然后再将其传递给抑制操作。这是用于生产的良好配置,因此您不会对本地磁盘或Kafka代理造成瓶颈。但这可能会让您在测试时感到惊讶。

通常,对于测试(或以交互方式尝试一些东西),我将禁用缓存并设置提交间隔,以最大程度地提高开发人员的理智程度:

properties.put(StreamsConfig.CACHE_MAX_BYTES_BUFFERING_CONFIG, 0);
properties.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, 100);

抱歉对Serde进行监督。希望我们能尽快解决KAFKA-7806的问题。

我希望这会有所帮助!

答案 1 :(得分:3)

KeySerde存在问题。由于WindowedBy操作会产生Windowed<String>类型的密钥,但是.suppress()使用的是默认密钥类型。

因此,您需要在调用count方法时在State存储区上定义KeySerde,如下所示:

      builder.stream<String,Double>inputTopic)
      .groupByKey()
      .windowedBy(TimeWindows.of(Duration.ofSeconds(15)))
      .count(Materialized.<String, Long, WindowStore<Bytes,byte[]>>as("count").withCachingDisabled().withKeySerde(Serdes.String()))
      .suppress(Suppressed.untilWindowCloses(BufferConfig.unbounded()))
      .toStream()
      . print(Printed.toSysOut());

答案 2 :(得分:0)

在创建流时添加Consumedbuilder.stream<String,Double>(inputTopic, Consumed.(Serdes.String(), Serdes.Double())