问题描述:

使用Spark SQL采用overwrite写法写入Hive(非分区表,),全量覆盖,因为人为原因脚本定时设置重复,SparkSql计算任务被短时间内调起两次,结果发现任务正常运行,造成写入表中数据结果存在同一张表有重复的行,数据翻倍。

hive 外部表 查询 hive外部表overwrite_Hive

从hdfs上可以看到也存在重复的的数据文件,会。有两组文件,每组大小是一样的。

hdfs dfs -ls /user/hive/warehouse/xxx.db/xxx_table

问题思考:

如果存在多个任务同时往一张Hive表overwrite,因为资源等因素,也会有时间差,基本不可能两个任务同时同秒执行结束,执行的写入sql都是insert overwrite,因此数据也理论上是正常写入数据之前要删除旧的数据,覆盖才合理。猜想是可能Hive本身会有延迟,在短时间内上一个任务还未insert overwrite结束,另外一个任务也紧跟着运行insert overwrite结束,导致重复插入数据没有被覆盖。

经过查资料发现:

Spark SQL在执行SQL的overwrite的时候并没有删除旧的的数据文件(Spark SQL生成的数据文件),Spark SQL写入Hive的流程如下:

hive 外部表 查询 hive外部表overwrite_hive 外部表 查询_02

(1)Spark写入Hive会先生成一个临时的_temporary目录用于存储生成的数据文件,全部生成完毕后全部移动到输出目录,然后删除_temporary目录,最后创建Hive元数据 (2)多个Spark写入数据任务使用了同一个_temporary目录,导致其中一个完成数据生成和移动到Hive路径之后删除_temporary目录失败(因为还有其他Spark任务在往里面写),进一步导致数据已经到了但是元数据没有创建
(3)上一个任务虽然生成了数据文件但是没有元数据,则后一个任务的overwrite找不到元数据因此无法删除Hive路径下的数据文件
(4)当最后一个执行完成的Spark插入任务结束后,此时Hive路径下已经移动过来多个任务的数据文件,由于已经没有正在执行的Spark写任务,因此删除_temporary目录成功,创建元数据成功,结果就是这个元数据对应了该Hive路径下所有版本的数据文件。

最后的话:

我的场景是全量覆盖,因为人为重复设置定时任务原因导致数据翻倍,仅仅是查明了原因所在。若小伙伴有业务需求需要同时两个SparkSql任务写入一个Hive表,可采用创建Hive分区表,同时执行两个SparkSQL任务,往同一个Hive表分区插入,结果正常。

查阅资料网上还有其他类似场景:

某张表的分区下的写入任务之前曾用SparkSQL执行过,跑失败后切换成HiveSQL执行成功了。查看该分区对应的目录,发现目录下同时存在SparkSQL和Hive SQL生成的文件(也就是Hive任务执行时未删除旧的sparkSQL的数据文件),因此导致查询有数据重复。

Hive 执行overwrite语句时是否删除旧数据机制:

当Hive执行insert overwrite写入数据到分区时,根据分区的元数据是否存在来决定是否要清空分区目录下的所有文件:

  1. 如果分区元数据存在(HiveMetaStore中有分区记录),则清空分区下的所有元数据
  2. 如果分区元数据不存在(仅针对外部表),Hive不会去自动推测分区对应的路径,也就不会去删除该分区下的所有文件.

排查HiveMetaStore的元数据,发现该分区的创建时间是在Hive SQL执行完后,可以推测出之前失败的SparkSQL任务虽然生成了数据文件,但是未生成对应的Hive元数据,因此出现这种情况。

SparkSQL 失败的原因

在Spark执行任务时,会创建一个临时目录,这个临时目录路径为 ${outputPath}/_temporary (outputPath为任务设置的OutputFormat的outputPath)。spark会将执行过程中生成的文件先落地到临时目录中,最终任务执行成功了才全部移动到最终的输出目录。最后,这个临时目录会在任务执行结束后被删除.

经测试发现,SparkSQL执行某张表的分区写入时,它生成的临时目录位于表路径下。如果这张表不是ORC或者Parquet表,它的临时目录就和Hive比较像,如/a/test/.hive-staging_hive_2020-10-23_16-41-55_549_7302943708666306032-5 (/a/test为表路径),如果这张表是ORC或者Parquet表,sparkSQL生成的临时目录就变成/a/test/_temporary。

上面业务写入的那张表是ORC表,因此如果有多个任务在同时写入,就会有问题:任务结束时spark要删除掉这个临时目录,而其他的任务也在使用这个临时目录,所以SparkSQL任务的错误原因就可以理通了:

比如任务A执行结束,要删除/a/test/_temporary这个临时目录,但是任务B还在执行,下面还有任务B的一些临时文件存在,这时候任务A执行的删除操作就会报错(如同Excel等文件正在使用过程中 你没法修改文件名)提示临时目录下还有其他的文件,因此无法删除临时目录。同时,SparkSQL在删除完临时目录后才会添加Hive元数据,因此这里删除临时目录失败就导致了后面的数据重复的问题。

    Hive在写入数据的时候也会创建临时目录,但是在非动态分区的写入模式下,Hive创建的临时目录是在具体的分区路径下,比如/user/warehouse/dt=20201022,因此各个分区的写入任务是可以同时并行的。