Spark Streaming任务GC严重的问题排查优化

1,795 阅读4分钟

近期接业务线反馈,在线运行的Spark Streaming流任务有gc告警,如果不处理任务就会挂掉,且之前也经常出现隔一段时间任务就失败退出的情况,需要综合排查。

业务背景

  1. 业务代码本身是没有致命逻辑错误的,程序可以正常的启动和运行较长一段时间,如一周两周;
  2. 流计算任务,从kafka读取数据计算后写;
  3. 没有使用第三方缓存承接中间数据;
  4. 业务数据量大小中等,没有显著流量洪峰

技术背景

  • Spark版本2.3
  • HBase版本2.0
  • Kafka版本1.1
  • 没有采用第三方缓存,如Redis来承接中间数据
  • 每个executor分配了60GB内存,20个cpu核心,一共有6个executor
  • 垃圾回收使用G1GC
  • 环境开启了Kerberos

排查与优化

日志分析

先前已建议开启debug级别日志,这也导致运行一段时间的日志量能达到50GB之多,正常运行时应调到info级别合适。

一般来说,性能问题在日志中的表现十分隐蔽难寻,或者干脆没有特定表征,除非累计之后引起了程序错误(ERROR),抛了异常。

GC细节

开启Spark的GC详情日志,在spark-env.sh文件中的SPARK_JAVA_OPTS参数上追加-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps,或者在spark-submit的任务提交命令上使用--conf spark.executor.extraJavaOptions来追加上述参数,随后在spark运行后的worker端日志中可以看到GC发生时打印的信息。

当给spark任务分配的内存不足时,会频繁发生minor gc(年轻代gc),如果存活时间长的对象特别多,就会发生full gc(老年代gc)。

当频繁的new对象时,导致很快进入老年代,这样也可能发生full gc。

GC选择上建议CMS,在JDK1.8之后的,也可以尝试G1GC,具体以在自己生产环境的效果对比为准。

资源侧调参

  1. 资源上,当前任务每个executor的核心数是20个,每个线程内存偏小,建议增加内存或降低核心数(因为该业务的计算量不大,cpu核不需要太多,这里给了20核已经足够多了)。

  2. 单个executor分配的内存超过了32GB,建议用户指定--conf spark.executor.extraJavaOptions="-XX:+UseNUMA -XX:+UseCompressedOops"来开启压缩指针。

    但据查,jvm的属性-XX:+UseCompressedOops在JDK 1.6和之后的版本都默认开启了,目前大部分使用jdk1.8或更新版本,所以上述操作可忽略。

  3. 如果可能的情况下,将executor的数量增大一倍,分配的内存减半(核数也减半),在流计算任务里往往受Kafka分区数的限制无法自由调整executor数量。

  4. 可选地,禁用JVM内存swap,可以添加启动参数-XX:+AlwaysPreTouch,执行sysctl -w vm.swappiness=0 禁用swap

  5. 为了降低GC时间长造成进程挂掉,可以加大executor的心跳超时时间,spark.executor.heartbeatInterval默认值10秒,可以调整到60秒(酌情)。

程序侧建议

  1. 设置较大的批处理的时间间隔。在业务允许的情况下,batchDuration大一些可以减少一些上下文创建销毁的消耗,降低任务挤压的可能性。
  2. 增大机器级别的分散度,也就是上面提到了资源一定的情况下,分多个executor使其分散到多台机器上去执行的效果更好。
  3. 非常重要!缓存多次使用的数据,尤其是参与Join计算的维度表数据,往往是从RDBMS等远程读取而来的,缓存起来能节省大量的时间。
  4. 强烈建议Streaming程序使用第三方的缓存库,如Redis来存储中间数据,否则日积月累下来可用内存会消耗殆尽。
  5. 使用可序列化的持久化策略(比如MEMORY_ONLY_SERMEMORY_DISK_SER),使对象占用内存空间更小。
  6. Spark默认的是使用Java内置的系列化类,可以使用Kryo序列化类库,进行序列化,因为kryo序列化方法可以进一步的降低RDD的partition的内存占用量。
  7. 还有一种表现是一个应用越跑越慢,可能的原因是在driver端存储了一个resultRDD,并在每批计算时更新,此时建议采用checkpoint来解决。
  8. 流任务的并发度可增大,当executor的数量超过kafka分区数的时候有用,多余的executor可以承担一些Task。

参考链接

  1. spark调优-GC - DavidZuo 提供了一些编码层面序列化的建议;
  2. Configuration - Spark 3.3.0 Documentation列出了spark环境变量相关参数
  3. Spark Streaming 流计算优化记录GC优化与shuffle service介绍了使用CMS回收器的实践