利用spark sql自适应功能避免小文件合并

1. 问题来源

离线数仓底层分了两层

  • 每天业务增量数据层(ODS):每天一个分区,用于存放业务每天的增量数据,
  • 每天业务快照层(SNAP):事实表一般无分区,保存业务的快照。昨天的业务快照,是前天的业务快照与昨天的增量数量合并去重后生成:昨天的业务快照=前天的业务快照+昨天的增量数据

insert overwrite table snap.${snap_table}
select
        ${snap_columns}
from
(
        select
                ${snap_columns},
                ROW_NUMBER() OVER ( PARTITION BY ${ods_primary} ORDER BY ${order_by} desc,date desc ) AS rank
        from
        (
                select
                        ${snap_columns},'${date}' as date
                from
                        ods.${ods_table}
                where
                        date='${date}'
                union all
                select
                        ${snap_columns},'19700101' as date
                from
                        snap.${snap_table}
                where
                        true
        ) a
) b
where rank = 1
"

可能存在小文件问题,一般任务完成成,可能还需要额外提交一个合并小文件任务。

2. 解决方案

2.1 方案一

额外新增一个合并小文件任务

这里用的是hive的合并小文件命令,其底层还是map+reduce,指定每个文件128M,但实际合并效果往往并不如意,有些可能100M,有些可能10M,合并后的文件不均衡,小文件可能还很多,效果不佳。

hive --hiveconf hive.merge.mapfiles=true --hiveconf  hive.merge.smallfiles.avgsize=67108864 --hiveconf hive.merge.size.per.task=134217728 --hiveconf  hive.exec.reducers.bytes.per.reducer=67108864 --hiveconf mapreduce.input.fileinputformat.split.maxsize=134217728 -e "ALTER TABLE snap.${snap_table} concatenate"

后续改进:上面的参数设置问题,合并后的小文件还是很多,每个文件大小不均匀,有些大有些小。经线上验证,下面的参数设置能达到预期

hive --hiveconf hive.merge.mapfiles=true --hiveconf mapred.max.split.size=134217728 --hiveconf mapred.min.split.size.per.node=134217728 --hiveconf mapred.min.split.size.per.rack=134217728 -e "ALTER TABLE snap.${snap_table} concatenate"

  • mapred.max.split.size:一个split最大值,默认为Long.MAX_VALUE = 9223372036854775807。
  • mapred.min.split.size.per.node:一个节点上split最小值
  • mapred.min.split.size.per.rack:一个交换机下split最小值

上面合并的主要思路是把输入目录下的文件分成多个map的输入, 并合并小文件,或者拆分大文件, 做为一个map的输入

  1. 根据输入目录下的每个文件,如果其长度超过mapred.max.split.size,以block为单位分成多个split(一个split是一个map的输入),每个split的长度都大于mapred.max.split.size,因为以block为单位, 因此也会大于blockSize,此文件剩下的长度如果大于mapred.min.split.size.per.node,则生成一个split,否则先暂时保留。
  2. 现在剩下的都是一些长度效短的碎片,把每个rack下碎片合并,只要长度超过mapred.max.split.size就合并成一个split,最后如果剩下的碎片比mapred.min.split.size.per.rack大,就合并成一个split,否则暂时保留。
  3. 把不同rack下的碎片合并,只要长度超过mapred.max.split.size就合并成一个split,剩下的碎片无论长度,合并成一个split。

举例:输入目录下五个文件:rack1下三个文件,长度为2050,1499,10;rack2下两个文件,长度为1010,80。另外blockSize为500.

  1. 第一步:生成五个split:1000,1000,1000,499,1000,剩下的碎片为rack1下50,10;rack2下10,80。
  2. 第二步:由于两个rack下的碎片和都不超过100,所以经过第二步,split和碎片都没有变化。
  3. 第三步:合并四个碎片成一个split,长度为150。

如果要减少map数量,可以调大mapred.max.split.size,否则调小即可.

 

 

 2.2 方案二

用pyspark开发合并小文件脚本

之前开发过一个工具,详见:https://github.com/haiping216/little_file_merge

 2.3 方案三

利用spark sql自适应功能

设置参数:

spark.sql.adaptive.enabled=true

spark.sql.adaptive.shuffle.targetPostShuffleInputSize=134217728

理论上只对最后shuffle操作的任务才生效,且只对最后一个shuffle才生效。

实践显示,设置参数后,自动合并后的文件大小平均,大小接近128M,符合预期,而且不需要另外一个map reduce任务做小文件合并,性能和耗时都有明显提升。

后续总结:这两个参数是spark旧的版本支持的,spark.sql.adaptive.enabled默认为false,官方默认不开启该功能。经多次测试验证,在小文件很多很小的情况下,有比较好的效果;但如果文件本来就不大比较均匀数量不多的情况下,效果并不理想,默认分区数优先,比如分区数是200,那么可能最终合并完,有200个小文件,每个文件只有40M,并不是预期的128M。

方案二中改进后的hive合并小文件,还是稳定可靠的。

你可能感兴趣的:(spark)