具有窗口的Kafka Streams拓扑不会触发状态更改

时间:2020-03-12 12:44:45

标签: apache-kafka apache-kafka-streams

我正在构建以下Kafka Streams拓扑(伪代码):

gK = builder.stream().gropuByKey();
g1 = gK.windowedBy(TimeWindows.of("PT1H")).reduce().mapValues().toStream().mapValues().selectKey();
g2 = gK.reduce().mapValues();
g1.leftJoin(g2).to();

如果您注意到了,这是一种菱形拓扑,从单个输入主题开始,以单个输出主题结束,消息流经两个并行流,最终在最后合并在一起。一种流适用(滚动?)加窗,另一种不适用。流的两个部分都在同一个键上工作(除了窗口中间引入的WindowedKey之外)。

我的消息的时间戳是事件时间。也就是说,它们是通过我的自定义配置TimestampExtractor实现从消息正文中选取的。我邮件中的实际时间戳是过去的几年。

在我的带有两个输入/输出消息的单元测试中以及在运行时环境(使用真实的Kafka)中,所有这些功能乍一看都很好。

当消息数量开始很大时(例如40K),问题似乎就出现了。

我失败的情况如下:

  1. 〜40K条记录,相同 键首先上传到输入主题

  2. 〜40K更新为 符合预期

  3. 另一个〜40K记录 与第1步相同但不同) 输入主题

  4. 只有大约100个更新来自输出主题, 而不是预期的新〜40K更新。没有什么特别的 看到这些〜100个更新,它们的内容似乎是正确的,但是 仅在某些时间范围内。对于其他时间窗口,没有 即使应明确定义流逻辑和输入数据也要进行更新 生成40K条记录。实际上,当我在步骤1中交换数据集时) 和3)我有完全相同的情况,来自〜40K更新 第二个数据集,与第一个数据集的编号相同〜100。

我可以在本地使用TopologyTestDriver的单元测试中轻松地重现此问题(但仅在大量输入记录上使用)。

在测试中,我尝试使用StreamsConfig.CACHE_MAX_BYTES_BUFFERING_CONFIG禁用缓存。不幸的是,这没有任何区别。

更新

我同时尝试了reduce()调用和aggregate()调用。在两种情况下,问题仍然存在。

我还需要注意的是,将StreamsConfig.TOPOLOGY_OPTIMIZATION设置为StreamsConfig.OPTIMIZE,如果没有将其设置,则在调试器之前之前的reduce()中调用mapValues()处理程序。 (或aggregate())处理函数,至少是第一次。我没想到。

不幸的是,尝试了join()和leftJoin()。 在调试器中,数据的第二部分根本不会触发“左”流程中的reduce()处理程序,但是会触发“右”流程中的reduce()处理程序。

使用我的配置,如果两个数据集中的数量或记录中的每个都为100,则问题不会自行显现,我将得到200条输出消息,正如我期望的那样。当我在每个数据集中将数字增加到200时,我收到的预期消息少于400。 因此,目前看来,诸如“旧”窗口之类的东西将被删除,而那些旧窗口的新记录将被流忽略。 有可以设置的窗口保留设置,但是我使用的默认值是我希望窗口保留其状态并保持活动至少12个小时(这远远超出了单元测试的时间)。

试图使用以下窗口存储配置修改左减速器:

Materialized.as(
    Stores.inMemoryWindowStore(
        "rollup-left-reduce",
        Duration.ofDays(5 * 365),
        Duration.ofHours(1), false)
)

结果仍然没有差异。

即使只有一个“左”流而没有“右”流,也没有join(),相同的问题仍然存在。看来问题出在我设置的窗口保留设置中。我的输入记录的时间戳(事件时间)跨越2年。第二个数据集再次从2年开始。 Kafka Streams中的这个位置可确保第二个数据集记录被忽略:

https://github.com/apache/kafka/blob/trunk/streams/src/main/java/org/apache/kafka/streams/state/internals/InMemoryWindowStore.java#L125

Kafka Streams版本为2.4.0。还使用Confluent依赖版本5.4.0。

我的问题是

  • 这种行为的原因可能是什么?
  • 我是否错过了流拓扑中的任何内容?
  • 这种拓扑是否可以正常工作?

1 个答案:

答案 0 :(得分:2)

经过一段时间的调试,我找到了造成问题的原因。

我的输入数据集包含带有跨2年时间戳记的记录。我正在加载第一个数据集,并从输入数据集中将流的“观察”时间设置为最大时间戳。

上传第二个数据集,该数据集的时间戳记在新的观察时间之前2年开始,导致内部流丢弃消息。如果您将Kafka日志记录设置为TRACE级别,则可以看到。

因此,要解决我的问题,我必须为Windows配置保留期和宽限期:

代替

.windowedBy(TimeWindows.of(windowSize))

我必须指定

.windowedBy(TimeWindows.of(windowSize).grace(Duration.ofDays(5 * 365)))

此外,我必须将reducer存储设置显式配置为:

 Materialized.as(
    Stores.inMemoryWindowStore(
        "rollup-left-reduce",
        Duration.ofDays(5 * 365),
        windowSize, false)
)

就是这样,输出就如预期的那样。