重新启动我的Kafka Streams应用程序时出现OutOfMemoryError

时间:2019-04-15 13:49:29

标签: java apache-kafka apache-kafka-streams

我有一个Kafka Streams应用程序(Kafka Streams 2.1 + Kafka broker 2.0),它基于TimeWindows进行聚合,并且我使用了抑制操作符来抑制结果的输出。

一切正常,直到我重新启动应用程序为止,它将按预期将KTABLE-SUPPRESS-STATE-STORE的偏移量重置为0以恢复抑制状态。但是每次我重新启动它时,它都会抛出一个OutOfMemoryError,我认为堆大小可能不够,所以我使用一个较大的Xmx/Xms,它可以进行一两次重新启动,然后{{ 1}}再次回来。现在OutOfMemoryError大约是20G,我认为这里不对。

代码段:

Xmx

我发现KTABLE-SUPPRESS-STATE-STORE中的记录键类似于1234567j.P,这是不可读的,但我想它是由SN和窗口结合生成的,我认为这会使KTABLE- SUPPRESS-STATE-STORE冗余,因为每个SN每个窗口都会有多个记录。

我有两个问题:

  1. 如果TimeWindows windows = TimeWindows.of(windowSize).until(retentionHours.toMillis()).grace(graceHours); KTable<Windowed<String>, MyStatistics> kTable = groupedBySerialNumber .windowedBy(windows) .aggregate(MyStatistics::new, (sn, resList, stats) -> stats.addResources(resList).updateSN(sn), Materialized.with(Serdes.String(), ArchiveSerdes.resourceStatistics())) .suppress(Suppressed.untilTimeLimit(timeToWait, Suppressed.BufferConfig.maxBytes(bufferMaxBytes))); 是否表示较小的堆大小,如果是,则如何限制速率,如果不是,则表示什么意思?
  2. KTABLE-SUPPRESS-STATE-STORE的密钥由哪个API定义,我该如何或应该控制它?

谢谢!

在2019/4/16编辑

错误堆栈跟踪为:

OutOfMemoryError

1 个答案:

答案 0 :(得分:1)

  

如果OutOfMemoryError指示的堆大小是否较小,那么,如何限制速率,如果不是,则意味着什么?

是的,没有足够的堆来分配应用程序需要操作的所有内存。我们不经常看到这种情况,抑制运算符是新出现的,所以我对此表示怀疑,但是请记住,基本上您的应用程序中的任何数据结构都可以负责。

诊断内存压力的最佳方法是执行“堆转储”。基本上,这会将JVM的整个内存复制到一个文件中,以便您可以使用https://www.eclipse.org/mat/之类的程序来分析其内容。这将是一个学习曲线,但是我认为您会发现通常可以使用一些分析内存使用情况的工具。

您可以随时触发堆转储(有几种方法可以完成,您必须研究最适合自己的方法)。但是我认为您会想利用Java的漂亮选项在发生内存不足错误时进行堆转储。这样,您更有可能肯定地找出罪魁祸首。请参阅https://docs.oracle.com/javase/7/docs/webnotes/tsg/TSG-VM/html/clopts.html#gbzrr或您的JVM的类似内容。

我可以推测出堆转储的原因,但是我恐怕会误入歧途,浪费时间。获得转储的结果后,我认为您应该继续并在Kafka问题跟踪程序https://issues.apache.org/jira/projects/KAFKA中打开错误报告。然后,我们可以帮助找出如何解决该错误以使您再次运行,以及如何在将来的版本中对其进行修复。

实际上,我将提供一个推测...您可能会看到此bug的结果:https://github.com/apache/kafka/pull/6536https://issues.apache.org/jira/browse/KAFKA-7895)。如果删除抑制运算符后OOME消失了,则可能暂时不要使用它。合并修补程序后,我将要求发布一个错误修正版本,您可以再次尝试查看问题是否已解决。

  

KTABLE-SUPPRESS-STATE-STORE的密钥由哪个API定义,我应该如何控制?

幸运的是,这有一个更简单的答案。您正在查看的键是记录键和窗口时间戳的二进制打包版本。该密钥是您使用windowBy的结果。在Java中,您可以看到聚合的结果是KTable<Windowed<String>, ...>,并且Suppress不会更改键或值的类型。换句话说,您正在查看密钥(Windowed<String>)的序列化版本。

静置一秒钟;假设您有两个序列号“ asdf”和“ zxcv”。假设您的窗口大小为一小时。您的应用程序在一天的每个小时内(分别)为这些序列号的每个事件进行分组。因此,从9:00到10:00的所有“ asdf”记录都有一个汇总,从9:00到10:00的所有“ zxcv”记录也有一个汇总。因此,窗口化KTable中的键总数为key space x number of windows being retained

Suppression运算符对KTable中的键数没有影响。其目的是在指定的时间(timeToWait)中禁止对这些键进行更新。例如,在不执行抑制的情况下,如果您在9:00和10:00之间对“ asdf”记录进行了3次更新,则窗口聚合每次都会为(asdf, 9:00)发出更新结果,因此对于其中的3个事件,您查看3个结果更新。 Suppress运算符仅阻止这些结果更新,直到通过timeToWait为止,并且当它通过时,它仅发出最新的更新。

因此,任何时候抑制缓冲区中的键数都小于上游KTable中的键总数。它仅包含在最近timeToWait时间内已更新的密钥。

有帮助吗?