一.引言
spark 代码执行任务时需要读取一个依赖文件,文件执行时该文件状态未知,有可能存在也有可能是空文件夹,遂增加 Try Catch 进行包装,当 sc.textFile 读取异常时,返回 emptyRdd,但是实际执行中,代码并未进入 Try Catch 区域,且报错异常栈显示在 foreachPartition 对应代码位置,遂开始修复之旅。
二.代码逻辑
先看下异常栈,然后再整理报错逻辑,这里忽略了中间的一些异常,自己的代码报错最终位置为 foreachPartition,行号为 109,报错信息为 matches 0 files 显然是 sc.textFile 读文件出错,所以这里第一个问题就是报错和代码不匹配:
org.apache.hadoop.mapred.InvalidInputException: Input Pattern /tmp/*/* matches 0 files
at org.apache.hadoop.mapred.FileInputFormat.singleThreadedListStatus(FileInputFormat.java:287)
at org.apache.hadoop.mapred.FileInputFormat.listStatus(FileInputFormat.java:229)
at org.apache.hadoop.mapred.FileInputFormat.getSplits(FileInputFormat.java:315)
at org.apache.spark.rdd.HadoopRDD.getPartitions(HadoopRDD.scala:204)
......
at org.apache.spark.rdd.RDD.foreachPartition(RDD.scala:978)
at com.aaa.bbb.ccc(xxx.scala:109)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
异常栈报错代码:
读取文件代码:
val infoRdd = try {
sc.textFile(path).map(line => {
line
})} catch {
case e: Exception => {
e.printStackTrace()
val emptyRdd: RDD[String] = sc.emptyRDD
emptyRdd
}
}
为什么 sc.textFile 的错误会报错在 foreachPartition 上:
因为 Spark 的懒加载机制,spark 构建运行图时发现要执行该 action 算子 foreachPartition 需要依赖上一步 userRdd,而 userRdd 需要依赖再上一层的 parentRdd,parentRdd 的构建需要读取 sc.textFile 读取 path 并加载,随后报错 matches 0 files,所以报错位置最后锁定在 foreachPartition 上。总计一下就是因为 Spark 的懒加载 lazy init。
三.异常解决
上面的问题其实很简单,就是读取文件时文件夹存在但为空,所以可以读取时进行判断,一种是实现 FileSystem,进行 fs.isEmpty 的判断,再进行后续的逻辑,还有一种比较简单,就是配置 spark 使其自动忽略空文件,报错异常栈在 Spark 代码的最后一个打印是:
at org.apache.spark.rdd.HadoopRDD.getPartitions(HadoopRDD.scala:204)
可以看到读取错误时有一个 ignoreMissingFiles 的参数,改参数会自动进入 catch 逻辑并返回一个空 RDD 保证后续执行没有问题,所以只需配置改参数即可:
--conf spark.files.ignoreMissingFiles=true \
修改参数后,109 行的 foreachParition 正常执行,懒加载的前置 Rdd 也执行正常未报错。