1, 环境
spark3.2.1 hadoop3.2.2
2, 问题现象
insert overwrite table到hive表时,出现路径不存在的报错,导致任务失败。
当表的路径在hdfs上时,没有问题。 表的路径在对象存储上时会有问题。
insert overwrite table带上分区路径也是没问题的。
具体报错:
org.apache.spark.sql.AnalysisException: Path does not exist: s3a://xxxx/hive/warehouse/tablename
at org.apache.spark.sql.errors.QueryCompilationErrors$.dataPathNotExistError(QueryCompilationErrors.scala:978)
at org.apache.spark.sql.execution.datasources.DataSource$.$anonfun$checkAndGlobPathIfNecessary$4(DataSource.scala:780)
at org.apache.spark.sql.execution.datasources.DataSource$.$anonfun$checkAndGlobPathIfNecessary$4$adapted(DataSource.scala:777)
3,问题原因分析
拿到的全量任务日志后发现,spark am失败了两次。第一次失败是因为task中数据转换异常导致,属于业务异常。 但是,yarn的容错机制将spark am第二次启动起来的时候,报了 Path does not exist的异常。
在spark inseroverwrite的执行逻辑中,是先将表的路径删除,然后再写入。并且删除路径的动作,是在driver侧完成的。 表路径的再次创建是在task执行的时候做的。 所以该问题就是为什么task没有再次创建对象存储的表路径。首先梳理下task的写入逻辑(默认使用fileoutcommitter的1.0版本的算法),task写入数据前,会建临时目录,将数据写入到临时目录。
当一个task成功执行完毕的时候,通过commiter的commit task逻辑,将数据从临时目录转移到中间目录。 当所有的task都成功完成后,driver会调用commiter的commit job逻辑,将中间目录的数据转移到最终路径,也就是表路径下。需要注意的是,上述流程中涉及到的目录或者路径,如果不存在的话,会自动创建。
对于表路径在hdfs上的表,临时目录,中间目录,都在表路径之下。所以就算task有业务异常,临时目录在异常发生前已经创建了,表的路径同时也被创建了。 当am第二次执行的时候,是不会报 Path does not exist错误的。
对于表路径在对象存储的表,临时目录为本地目录,类似于/xx/xxx这种的。所以task有业务异常,没有创建表路径。 为什么临时目录为本地目录,因为该任务使用s3a协议去commit task和commit job。
所以问题就在于s3a协议上。
4,问题解决方案
不使用s3a协议去执行insert overwrite table。
set spark.sql.sources.commitProtocolClass=org.apache.spark.sql.execution.datasources.SQLHadoopMapReduceCommitProtocol;
set spark.sql.sources.outputCommitterClass=;
注意:不实用s3a协议的话,会严重影响commit性能,因为对象存储的mv或rename,都是先copy再delete。