一、问题描述
为了支撑相应的业务需求,本次生产环境通过Hive SQL来完成动态插入分区表数据的脚本开发。但是,动态分区的插入往往会伴随产生大量的小文件的发生。而小文件产生过多的影响主要分为以下两种情况:
(1) 从Hive的角度看,小文件会开很多map,一个map开一个JVM去执行,所以这些任务的初始化,启动,执行会浪费大量的资源,严重影响性能。
(2)在HDFS中,每个小文件对象约占150byte,如果小文件过多会占用大量内存。这样NameNode内存容量严重制约了集群的扩展。
所以,我们必须要解决上述小文件过多的情况,可以从输入、中间过程和输出来入手进行优化。
二、知识概括
1.简要描述动态分区
所谓的动态分区,就是对分区表insert数据时候,数据库自动会根据分区字段的值,将数据插入到相应的分区中,Hive也提供了类似的机制,即动态分区。
|
[注意] hive使用动态分区,需要进行相应的配置。
2. 动态分区相关配置项介绍
|
3.产生小文件的原因:
Spark程序以动态分区的方式写入Hive表时,会出现了大量的小文件,导致最后移动文件到hive表目录非常耗时,这是因为在Shuffle时Hive多个分区的数据随机落到Spark的多个Task中,此时Task与Hive分区数据的关系是多对多,即每个Task会包含多个分区的部分数据,每个Task中包含的每个分区的数据都很少,最终会导致Task写多个分区文件,每个分区文件都比较小。
为了减少小文件的数量,需要将数据按照分区字段进行Shuffle,将各个分区的数据尽量各自集中在一个Task,在Spark SQL中就是通过distribute by关键字来完成这个功能的。
当使用distribute by关键字在后出现了数据倾斜,即有的分区数据多,有的分区数据少,也会导致spark 作业整体耗时长。需要在distribute by后面增加随机数,例如:
insert overwrite table target partition(p1,p2)select * from sourcedistribute by p1, p2, cast(rand() * N as int)
N值可以在文件数量和倾斜度之间做权衡。
在Spark SQL中使用动态分区写入数据,需要同时使用distribute by和sort by才能有效减少小文件数量。
4.解决办法:
distribute by:
少用动态分区,如果场景下必须使用时,那么记得在SQL语句最后添加上distribute by
假设现在有20个分区,我们可以将dt(分区键)相同的数据放到同一个Reduce处理,这样最多也就产生20个文件,dt相同的数据放到同一个Reduce可以使用DISTRIBUTE BY dt实现,所以修改之后的SQL如下:
insert overwrite table temp.wlb_tmp_smallfile partition(dt)
select * from process_table
DISTRIBUTE BY dt;
修改完之后的SQL运行良好,并没有出现上面的异常信息,但是这里也有个问题,这20个分区目录下每个都只有一个文件!!这样用计算框架(MR/Spark)读取计算时,Mapper/Task数量根据文件数而定,并发度上不去,直接导致了这个SQL运行的速度很慢
如果想要具体最后落地生成多少个文件数,使用 distribute by cast( rand * N as int) 这里的N是指具体最后落地生成多少个文件数,那么最终就是每个分区目录下生成7个 文件大小基本一致的文件。修改的SQL如下
insert overwrite table temp.wlb_smallfile partition(dt)
select * from process_table
distribute by cast(rand()*7 as int);
合理调整Reduce端的读取的文件的大小:
-- 在 map only 的任务结束时合并小文件
set hive.merge.mapfiles = true;
-- 在 MapReduce 的任务结束时合并小文件
set hive.merge.mapredfiles = true;
-- 作业结束时合并文件的大小
set hive.merge.size.per.task = 256000000;
-- 每个Map最大输入大小(这个值决定了合并后文件的数量)
set mapred.max.split.size=256000000;
-- 每个reducer的大小, 默认是1G,输入文件如果是10G,那么就会起10个reducer;
set hive.exec.reducers.bytes.per.reducer=1073741824;
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;