Spark 中一些常见的 troubleshooting

记录一些常见的 Spark 作业里的 troubleshooting。

1,shuffle reduce 端缓冲大小导致的 OOM

在 shuffle 过程中,reduce 端在拉取 map 端的数据时,每个 task 会有自己的 缓冲区用来存放每次拉取的数据,这个缓冲区默认是 48M。

如果 map 端产生的数据量非常大,并且 map 端的写入数据非常快,那么这时候 reduce 端的所有 task 的缓冲区可能会出现满负荷存储,每个 task 的缓冲区都会被写满,那么会导致 task 在计算过程中读取的数据量很大,但每个 executor 在当前 JVM 进程中分配的内存是死的(0.2占比),这时候大量的 task 计算产生的对象很可能会撑爆内存,导致 OOM。

这时候我们可以通过调小 reduce 端的缓冲区内存大小,让 task 多拉取几次,多些网络传输的性能消耗,但可以保证 reduce 端的内存够用。

2,JVM 的 GC 导致 shuffle 文件拉取失败

Spark 作业中有时会出现的一种情况:shuffle file not found,还是挺常见的。而且有的时候,出现这种情况以后,会重新去提交stage、task。重新执行一遍,发现就好了。没有这种错误了。

这是因为前一个 stage 在 shuffle 时候内存消耗太大,频繁的 gc,每当触发 gc 的时候,整个 JVM 进程都会停止作业。而在这个时候下一个 stage 的 executor 要拉取需要用的数据,这时候就会拉不到,这时候会尝试等待一会,再次拉取,有时候刚好等待时间后,前一个 stage 的 gc 结束,这时候就能拉到数据了。

这种情况可以调整这两个参数:

1,spark.shuffle.io.maxRetries shuffle 拉取失败后重试的次数,默认是 3 次;

2,spark.shuffle.io.retryWait 每次重试拉取文件的时间间隔,默认是 5s;

3,Yarn 集群资源不足导致 application 直接失败

一个 Yarn 集群中可能已经跑了一个 spark 作业,这时候你又提交一个 spark 作业,两个 spark 作业设置的资源刚好等于或者稍小于整个集群的资源,这时候很容易导致跑不了第二个 spark 作业,因为 spark 作业跑起来之后耗费的资源很有可能大于我们在 shell 脚本里分配的资源。

为了防止这样的事情发生,有这几种方案:

1,在 J2EE 中做限制,同时只能提交一个 Spark 作业到 Yarn 集群上去执行,这样确保一个 Spark 作业的资源够用就行;

2,使用队列的方式执行 Spark 作业,保证每个 Spark 作业分配的资源到最大化,确保每个 Spark 作业在最短的时间内执行完毕;

4,序列化问题导致的报错

用 client 模式提交 Spark 作业时,本地打印的 log 中出现类似于 Serializable 之类的报错。这种报错是由于某类没有实现序列化接口导致的错误。

常见的需要序列化的地方有这两个:

1,算子函数汇总使用到外部的自定义类型的变量;

2,如果要将自定义的类型,作为 RDD 的元素类型,这个类型也要实现序列化接口;

5,Yarn-client 模式下导致网卡流量过大

生产环境下如果使用 Yarn-client 模式跑 Spark 作业,由于 driver 进程跑在本地设备上,大量的数据传输会导致本地设备的网卡流量十分的大。

所以生产环境要使用 Yarn-cluster 模式。

6,Yarn-cluster 模式下 JVM 内存溢出

有时候,运行了一些包含了 Spark sql 的 Spark 作业,可能会碰到在 Yarn-client 模式下正常运行,但在 Yarn-cluster 模式下,会报 JVM 的 PermGen(永久代)的内存溢出问题。

因为 Yarn-client 模式下,driver 是运行在本地设备上的,Spark 使用的 JVM 的 PermGen 的配置是本地的 spark class 文件,JVM 的永久带大小为 128M。但在 Yarn-cluster 模式下,drvier 是运行在 Yarn 集群的某个节点上的,默认的大小是 82M。

Spark sql 的内部有很多复杂的 SQL 语义解析,语法数转换等等,特别复杂。在这种情况下,如果 sql 写的比较发咋的话,会导致内存的消耗,对永久代的占用会比较大,容易超过 Yarn 集群上的设置的默认值。

解决方案:

spark-submit脚本中,加入以下配置即可:

--conf spark.driver.extraJavaOptions="-XX:PermSize=128M -XX:MaxPermSize=256M"

这个就设置了 driver 永久代的大小,默认是 128M,最大是 256M。那么,这样的话,就可以基本保证你的 Spark 作业不会出现上述的 yarn-cluster 模式导致的永久代内存溢出的问题。