Kubernetes上Apache Spark结构化流上的长时间GC暂停

时间:2018-08-27 16:06:22

标签: apache-spark kubernetes garbage-collection jvm apache-spark-sql

我正在尝试使用在Kubernetes上运行的Apache Spark 2.3 Scala API扩展结构化流管道。工作的基本流程是这样的:

  • 读取包含约1,000,000条记录的静态数据集,这些记录将各个源ID映射到输出聚合
  • 从Kafka读取流数据集,其中包含要汇总的时间序列指标,并映射到其源ID
  • 根据源ID对每个数据集进行分区
  • 在源ID上加入2个数据集(这会将指标映射到正确的输出聚合,同时还从kafka过滤掉了不应聚合的数据)
  • 应用水印
  • 删除重复项
  • 汇总数据
  • 写入Kafka输出接收器

我在Kubernetes上运行,并配置了一个集群,其中包含30个执行器,每个执行器具有3个内核。 Kafka当前每个源ID每秒流式传输600000个指标,并配置了600个分区。我正在尝试将所有这些汇总到10个不同的输出中(即每个输出汇总由60000个不同的源ID组成)。我每10秒就有一次管道触发器来处理来自Kafka的〜6,000,000条记录。我的聚合窗口是1分钟不重叠的,并且我的水印设置为30秒。理想情况下,我希望使用更长的水印来解释延迟到达的数据,但是删除重复/水印阶段似乎是一个瓶颈,尤其是在调用垃圾收集器时。这是我最近运行的管道中的一些数据:

Processed And Input Rows Per Second

该图显示,管道在每秒大约8-9分钟的时间内保持每秒输入行的速度,但随后橙色线下降到绿色线(时间轴上的〜10:01)以下,并且管道很难时间跟上输入数据速率。我查看了Spark UI,了解了为什么会出现速度下降的线索,并发现在执行重复复制/水印阶段,一名执行者需要55秒来执行GC。以下是该阶段的摘要统计信息以及事件时间轴的放大图:

我尝试了here建议的多种技术,但结果却不尽相同。特别是:

  • Kryo序列化似乎影响不大。
  • 使用这些设置-XX:+ UseG1GC -XX:MaxGCPauseMillis = 500,可以减少长时间停顿的频率,但仍然会发生。
  • 我打开了GC日志,并通过gceasy处理了它们,并尝试遵循他们的建议。这表明Full GC事件导致了长时间的停顿,并且日志未显示出增加GC线程数会有所帮助的症状。平均创建速度为182.18 mb / sec,平均提升速度为49.8 mb / sec
  • 我尝试将NewRatio减小为1,但这会导致更频繁的长时间停顿,而持续时间更短(即每次停顿〜25秒,而不是50+秒)
  • 很难知道我的流数据集正在使用多少内存,因为如果我尝试对其进行缓存,则会出错。

其余的内存建议类似于“尝试修改此参数或该参数”,但是尝试每个排列都很困难,并且没有指出我应该期待的行为。有人可以指出我下一步的方向吗?我觉得GC的55秒是不合理的,应该有一些方法可以对其进行调整,以免我的工作受到1位执行者的阻碍。

1 个答案:

答案 0 :(得分:1)

因此,当我脑海中浮现出新的解决方案时,我应该早点回答此问题,但最终我做了一些事情,从而减少了垃圾回收时间。我不记得所有有助于解决此问题的文档资源,但是我花了很多时间研究SO,gceasy建议和一般Java GC文献。无论如何,这就是最终的帮助:

  • 参加完整GC活动的内核数量有限:我相信这是提高性能的最大原因。我注意到,某些执行器在给定的微型批处理中将具有较大的GC时间,而同一kubernetes VM上的其他执行器将具有较大的计算时间,该时间接近(如果不完全)GC暂停的持续时间。这种相关性使我走上了一条研究之路,最终我发现JVM(至少对于Java 8是)从底层的kubernetes VM中获得了GC的默认值,而不是专用于运行JVM的容器的有限资源。由于每个容器具有不同的JVM实例,因此每个执行程序都具有默认的GC参数,并假定它是在基础kubernetes VM上运行的唯一JVM。指定Full GC事件可用线程数的GC参数是ParallelGCThreads。缺省情况下,JVM将其设置为VM上内核总数的百分比。如果我没记错的话,对于一个32核kubernetes VM,最终它是23。因此,当发生Full GC事件时,GC会引起正在执行正常计算的其他执行程序正在使用的CPU上的争用。我的理论是,这种争论推动了在同一基础kubernetes VM上发生的GC /计算运行时。对于我的特定测试,由于我在每32个核心kubernetes VM中运行6个执行程序,因此最终覆盖了ConcGCThreads(至1)和ParallelGCThreads(至5)的默认参数。
  • 增加每个执行器上的内存: gceasy图形从未真正显示出内存平稳期。只是随着管道的继续运行而增加了。我最终将每个执行器的专用内存从8 GB增加到了〜15 GB,此后又达到了约10 GB的平稳状态。您需要的实际内存量可能取决于您的代码。
  • 启用了字符串重复数据删除:我的大部分数据集都是字符串,因此这有助于减少应用程序的总体内存占用量
  • 修改了初始堆占用率:在gceasy和某些SO线程中建议这样做。

因此,这是我正在使用的最后一组JVM参数。我希望这会有所帮助。

-XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:InitiatingHeapOccupancyPercent=35 -XX:+UseStringDeduplication -XX:ConcGCThreads=1 -XX:ParallelGCThreads=5