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

Sea*_*n O 5 garbage-collection jvm apache-spark kubernetes apache-spark-sql

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

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

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

每秒处理和输入行数

该图显示管道每秒跟上输入行大约 8-9 分钟,但随后橙色线下降到绿线以下(时间轴上的~10:01)并且管道很难跟上与输入数据速率。我查看了 Spark UI 以寻找有关为什么会发生减速的线索,并发现一个执行程序在删除重复项/水印阶段需要 55 秒来执行 GC。以下是舞台的汇总统计数据和事件时间线的放大图:

我尝试了此处建议的多种技术,但结果喜忧参半。特别是:

  • Kryo 连载似乎没什么效果。
  • 使用这些设置 -XX:+UseG1GC -XX:MaxGCPauseMillis=500,可以减少长时间停顿的频率,但它们仍然会发生。
  • 我打开 GC 日志并通过gceasy处理它们并尝试遵循他们的建议。这表明长时间暂停来自 Full GC 事件,并且日志没有显示增加 GC 线程数会有所帮助的症状。平均创建率为 182.18 mb/sec,平均提升率为 49.8 mb/sec
  • 我尝试将 NewRatio 减少到 1,但这导致更频繁的长时间暂停和更短的持续时间(即每次暂停约 25 秒而不是 50+ 秒)
  • 很难知道我的流式数据集使用了多少内存,因为如果我尝试缓存它,就会出现错误。

其余的内存建议就像“尝试修改这个参数或那个参数”,但是很难尝试每一个排列,而且它没有表明我应该期待什么行为。有人可以指出我下一步要遵循的方向吗?我觉得 55 秒的 GC 是不合理的,应该有一些方法来调整它,这样我的工作就不会受到 1 个执行者的阻碍。

Sea*_*n O 4

因此,当我对解决方案记忆犹新时,我应该早点回复这个问题,但我最终做了一些有助于减少垃圾收集时间的事情。我不记得帮助我解决这个问题的所有文档来源,但我花了很多时间研究 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 虚拟机,如果我没记错的话,最终是 23 核。因此,当发生 Full GC 事件时,GC 会导致其他正在进行正常计算的执行器使用的 CPU 发生争用。我的理论是,这种争用推高了同一底层 kubernetes VM 上发生的 GC/计算运行时间。对于我的特定测试,我最终覆盖了 ConcGCThreads(至 1)和 ParallelGCThreads(至 5)的默认参数,因为我在每个 32 核 kubernetes 虚拟机上运行 6 个执行程序。
  • 增加每个执行器上的内存: 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
Run Code Online (Sandbox Code Playgroud)