Kafka Streams(禁止):重新启动拓扑

时间:2019-06-08 19:35:58

标签: apache-kafka-streams suppress

我发现了两个问题,如果分区中没有新记录,为什么不发出结果记录:
1. "Kafka Stream Suppress session-windowed-aggregation"
2. "Kafka Streams (Suppress): Closing a TimeWindow by timeout"

在回答这两个问题时,解释是必须发送新记录才能发出记录。

我不明白,为什么在超时后发出没有新记录的记录会违反禁止合同,并希望得到解释。

到目前为止,最好的建议是使用虚拟记录来触发排放。

我认为关闭和重新启动流(拓扑)可能比编写伪记录更合适。我认为流的新实例将使记录达到最高峰,并在超时到期后发出结果。

但是,我尝试并发现它不起作用。如果可以的话,请您解释一下。

@Slf4j
public class KafkaStreamVerticle extends AbstractVerticle {

  private KafkaStreams streams;

  @Override
  public void start(Future<Void> startFuture) throws Exception {

    Single.fromCallable(() -> getStreamConfiguration()).subscribe(config -> {

      final StreamsBuilder builder = new StreamsBuilder();

      builder.<String, String>stream(KafkaProducerVerticle.TOPIC)
          .flatMapValues((k, v) -> List.<JsonObject>of(new JsonObject(v).put("origKey", k)))
          .selectKey((k, v) -> v.getString(KafkaProducerVerticle.CATEGORY))
          .flatMapValues(v -> List.<String>of(v.toString()))
          .groupByKey(Grouped.with(Serdes.String(), Serdes.String()))
          .windowedBy(TimeWindows.of(Duration.ofSeconds(4)).grace(Duration.ZERO)).count()
          // .suppress(Suppressed.untilWindowCloses(BufferConfig.unbounded())).toStream().foreach((k,
          .suppress(Suppressed.untilTimeLimit(Duration.ofSeconds(4), BufferConfig.unbounded()))
          .toStream().foreach((k, v) -> log.info("********* {}: {} - {}: {}", k.key(),
              k.window().start(), k.window().end(), v));

      streams = buildAndStartsNewStreamsInstance(config, builder);
      Runtime.getRuntime().addShutdownHook(new Thread(streams::close));
      restartStreamsPeriodicaly(config, builder, 30_000L);
      log.info("consumer deployed");
      startFuture.complete();
    });
  }

  private KafkaStreams buildAndStartsNewStreamsInstance(Properties config,
      final StreamsBuilder builder) {
    KafkaStreams streams = new KafkaStreams(builder.build(), config);
    streams.cleanUp();
    streams.start();
    return streams;
  }

  private void restartStreamsPeriodicaly(Properties config, final StreamsBuilder builder,
      @NonNull Long period) {
    vertx.setPeriodic(period, l -> {
      log.info("restarting streams!!");
      streams.close();
      streams = buildAndStartsNewStreamsInstance(config, builder);
    });
  }

  private Properties getStreamConfiguration() {
    final Properties streamsConfiguration = new Properties();
    streamsConfiguration.put(StreamsConfig.APPLICATION_ID_CONFIG, "suppress-example");
    streamsConfiguration.put(StreamsConfig.CLIENT_ID_CONFIG, "suppress-client");
    streamsConfiguration.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
    streamsConfiguration.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG,
        Serdes.String().getClass().getName());
    streamsConfiguration.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG,
        Serdes.String().getClass().getName());
    streamsConfiguration.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
    streamsConfiguration.put(StreamsConfig.STATE_DIR_CONFIG, "/tmp/kafka-streams");
    streamsConfiguration.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, 10);
    streamsConfiguration.put(StreamsConfig.CACHE_MAX_BYTES_BUFFERING_CONFIG, 0L);
    return streamsConfiguration;
  }
}

1 个答案:

答案 0 :(得分:0)

Kafka Stream提供事件时间语义,这意味着,它的内部时间仅基于记录的时间戳而提前(内部时间从不基于壁钟时间提前)。您正在执行的“超时”,它也基于事件时间(而不是挂钟时间)。

假设您有一个大小为5的窗口(即[0,5)是一个窗口),并且看到的数据为ts = 1,2,3。这意味着下一条记录可能具有timestamp = 4,并且必须包含在窗口中。但是,如果没有新数据到达,则无论等待多长时间,都不会发出窗口结果。仅当timestamp = 5的记录到达时,内部时间才会前进,并且现在大于窗口结束时间,并且会发出窗口结果。如果prevent()将在基于墙上时钟的超时后发出数据,并且下一条记录的时间戳为4,则它将发出错误的结果。

此外,prevent()会记住其内部状态和时间。因此,即使您重新启动应用程序,prevent()仍将缓冲数据,并仍将等待timestamp = 5的记录来发出数据。