如何实际丢弃迟到的记录?

时间:2017-03-27 05:39:00

标签: apache-kafka apache-kafka-streams

问题在于:

假设有一个数字流,我想在1小时的水桶中从这些数字中收集MAX,我在这个水桶上允许最多3小时的延迟。

这听起来像是tumbling windows的实验室案例。

这是我到目前为止所拥有的:

stream.aggregate(
      () -> 0L,
      (aggKey, value, aggregate) -> Math.max(value, aggregate),
      TimeWindows.of(TimeUnit.HOURS.toMillis(1L)).until(TimeUnit.HOURS.toMillis(3L)),
      Serdes.Long(),
      "my_store"
)

首先,我无法验证这是否真的发生了测试。时间戳是通过TimestampExtractor提取的,我用Thread.sleep模拟延迟(我将窗口设置为较小的值以进行测试),但是"后期记录"仍处理而不是丢弃。

在常规窗口上似乎很少(没有?)示例。有一个关于SessionWindows的集成测试,但就是这样。我能正确理解这些概念吗?

编辑2

示例JUnit测试。由于它很大,我通过Gist分享它。

https://gist.github.com/Hartimer/6018a731753846c1930429716703e5a6

编辑(添加更多代码)

数据点有一个时间戳(收集数据的时间),收集数据的机器的主机名和值。

{
    "collectedAt": 12314124134, // timestamp
    "hostname": "machine-1",
    "reading": 3
}

自定义时间戳提取器用于获取collectedAt。这是我的管道的更完整的表示:

source.map(this::fixKey) // Associates record with a key like "<timestamp>:<hostname>"
  .groupByKey(Serdes.String(), roundDataSerde)
  .aggregate(
          () -> RoundData.EMPTY_ROUND,
          (aggKey, value, aggregate) -> max(value, aggregate),
          TimeWindows.of(TimeUnit.HOURS.toMillis(1L))
                     .until(TimeUnit.SECONDS.toMillis(1L)), // For testing I allow 1 second delay
          roundDataSerde,
          "entries_store"
  )
  .toStream()
  .map(this::simpleRoundDataToAggregate) // Associates record with a key like "<timestamp floored to nearest hour>"
  .groupByKey(aggregateSerde, aggregateSerde)
  .aggregate(
          () -> MyAggregate.EMPTY,
          (aggKey, value, aggregate) -> aggregate.merge(value), // I know this is not idempotent, that's a WIP
          TimeWindows.of(TimeUnit.HOURS.toMillis(1L))
                     .until(TimeUnit.SECONDS.toMillis(1L)), // For testing I allow 1 second delay
          aggregateSerde,
          "result_store"
  )
  .print()

该测试的片段是

Instant roundId = Instant.now().truncatedTo(ChronoUnit.HOURS).minus(9L, ChronoUnit.HOURS);
    sendRecord("mytopic", roundId, 3);
    sendRecord("mytopic", roundId.plusMillis(15000), 2);

    log.info("Waiting a little before sending more usage. (simulating late record)");
    Thread.sleep(5000L);

    sendRecord("mytopic", roundId.plusMillis(30000), 5);

    // Assert stored value is "3".
    // It actually is 5 because the last round is accounted for

非常感谢任何帮助。

2 个答案:

答案 0 :(得分:3)

认为 Hartimer的自我答案实际上是错误的。让我试着解释一下发生了什么,至少根据我自己的知识。 : - )

  • 根据通过配置的时间戳提取器为您的应用程序配置的时间语义来处理延迟到达的数据。在@ Hartimer的情况下,这是事件时间(此处使用自定义时间戳提取器)。
  • FWIW,在处理时间的情况下,根据定义,没有迟到的记录:每个记录到达&#34;只是在时间&#34;。 A&#34;迟到&#34;记录(再次,在此上下文中没有此类记录)包含在当前窗口中,但从未装配到先前的窗口中。
  • 设置窗口保留时间的调用TimeWindows#until()是保留时间的下限。卡夫卡可能会在一个窗口附近停留一段时间&#34; (我有意模糊,见下文)比配置的保留时间。因此,像@Hartimer这样的严格测试可能无法产生人们可能直观的结果。

关于窗口保留时间为下限的幕后实际发生的事情有点棘手(可能超出了这个问题的范围),所以我不想试图解释一下除非有特殊要求我这样做。

更新:此外,问题代码段中的此代码甚至无法正常工作,因为它应该抛出IllegalArgumentException

TimeWindows.of(TimeUnit.HOURS.toMillis(1L))
           .until(TimeUnit.SECONDS.toMillis(1L))

要求是,对于各自的输入参数until() >= of()。您不能定义一个大小为1小时但保留期仅为1秒的窗口(此处的保留时间必须> = 1小时)。

更新2:幕后发生的事情是TimeWindows#until()的设置用于创建/管理本地窗口商店的段文件。只要窗口的片段存在,就会接受该窗口的迟到记录。我将跳过有关如何删除/过期细分的部分,因为我真的需要深入研究代码(我不知道我的头脑)。

答案 1 :(得分:0)

我相信我发现了自己的问题。它归结为TimestampExtractor以及我用来评估&#34;延迟记录&#34;的价值。

在Kafka Stream术语中,有三个&#34;次&#34; (see here):

  • 活动时间:录制数据的时间
  • 处理时间:流处理器收到的数据是什么时候
  • 摄取时间 :(与问题无关)

在我的示例中,我实际上是使用事件时间来确定是否有某些事情被延迟,但这并不能代表迟到的记录。收集数据的人会将此值设置为当地的时间感(至少在我的用例中)。

重要日期是处理时间。无论何时生成,我们都会花多长时间接收该事件。我的聚合已经按照&#34;事件时间&#34;。

处理分组

我创建了一个新的Gist,其中包含现在通过的测试的更新版本。添加了一个额外的字段receivedAt,模拟&#34;处理时间&#34;。

https://gist.github.com/Hartimer/c79569ad517ab95d08dbe8e84bfa6789