1.动态分区插入数据,会产生大量小文件
2.数据源本来就含有大量小文件
3.数据增量导入,如Sqoop数据导入,增量insert导入数据等
4.分桶表(主要是切分文件,容易产生小文件问题)
1.2.3都是数据导入的问题:
hive 中的小文件肯定是向 hive 表中导入数据时产生,所以先看下向 hive 中导入数据的几种方式
1.直接向表中插入数据
insert into table A values (1,'zhangsan',88),(2,'lisi',61);
这种方式每次插入时都会产生一个文件,多次插入少量数据就会出现多个小文件,但是这种方式生产环境很少使用,可以说基本没有使用的
2.通过load方式加载数据
load data local inpath '/export/score.csv' overwrite into table A -- 导入文件
load data local inpath '/export/score' overwrite into table A -- 导入文件夹
使用 load 方式可以导入文件或文件夹,当导入一个文件时,hive表就有一个文件,当导入文件夹时,hive表的文件数量为文件夹下所有文件的数量
3.通过查询方式加载数据
insert overwrite table A select s_id,c_name,s_score from B;
这种方式是生产环境中常用的,也是最容易产生小文件的方式
insert 导入数据时会启动 MR 任务,MR中 reduce 有多少个就输出多少个文件
所以, 文件数量=ReduceTask数量*分区数
也有很多简单任务没有reduce,只有map阶段,则
文件数量=MapTask数量*分区数
每执行一次 insert 时hive中至少产生一个文件,因为 insert 导入时至少会有一个MapTask。
像有的业务需要每10分钟就要把数据同步到 hive 中,这样产生的文件就会很多。
#对于非分区表
alter table A concatenate;
#对于分区表
alter table B partition(day=20201224) concatenate;
例如:
#向 A 表中插入数据
hive (default)> insert into table A values (1,'aa',67),(2,'bb',87);
hive (default)> insert into table A values (3,'cc',67),(4,'dd',87);
hive (default)> insert into table A values (5,'ee',67),(6,'ff',87);
#执行以上三条语句,则A表下就会有三个小文件,在hive命令行执行如下语句
#查看A表下文件数量
hive (default)> dfs -ls /user/hive/warehouse/A;
Found 3 items
-rwxr-xr-x 3 root supergroup 378 2020-12-24 14:46 /user/hive/warehouse/A/000000_0
-rwxr-xr-x 3 root supergroup 378 2020-12-24 14:47 /user/hive/warehouse/A/000000_0_copy_1
-rwxr-xr-x 3 root supergroup 378 2020-12-24 14:48 /user/hive/warehouse/A/000000_0_copy_2
#可以看到有三个小文件,然后使用 concatenate 进行合并
hive (default)> alter table A concatenate;
#再次查看A表下文件数量
hive (default)> dfs -ls /user/hive/warehouse/A;
Found 1 items
-rwxr-xr-x 3 root supergroup 778 2020-12-24 14:59 /user/hive/warehouse/A/000000_0
#已合并成一个文件
注意:
1、concatenate 命令只支持 RCFILE 和 ORC 文件类型。
2、使用concatenate命令合并小文件时不能指定合并后的文件数量,但可以多次执行该 命令。
3、当多次使用concatenate后文件数量不在变化,这个跟参数 mapreduce.input.fileinputformat.split.minsize=256mb 的设置有关,可设定每个文件 的最小size。
设置map输入合并小文件的相关参数:
#执行Map前进行小文件合并
#CombineHiveInputFormat底层是 Hadoop的 CombineFileInputFormat 方法
#此方法是在mapper中将多个文件合成一个split作为输入
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat; -- 默认
#每个Map最大输入大小(这个值决定了合并后文件的数量)
set mapred.max.split.size=256000000; -- 256M
#一个节点上split的至少的大小(这个值决定了多个DataNode上的文件是否需要合并)
set mapred.min.split.size.per.node=100000000; -- 100M
#一个交换机下split的至少的大小(这个值决定了多个交换机上的文件是否需要合并)
set mapred.min.split.size.per.rack=100000000; -- 100M
设置map输出和reduce输出进行合并的相关参数:
#设置map端输出进行合并,默认为true
set hive.merge.mapfiles = true;
#设置reduce端输出进行合并,默认为false
set hive.merge.mapredfiles = true;
#设置合并文件的大小
set hive.merge.size.per.task = 256*1000*1000; -- 256M
#当输出文件的平均大小小于该值时,启动一个独立的MapReduce任务进行文件merge
set hive.merge.smallfiles.avgsize=16000000; -- 16M
启用压缩
# hive的查询结果输出是否进行压缩
set hive.exec.compress.output=true;
# MapReduce Job的结果输出是否使用压缩
set mapreduce.output.fileoutputformat.compress=true;
#reduce 的个数决定了输出的文件的个数,所以可以调整reduce的个数控制hive表的文件数量,
#hive中的分区函数 distribute by 正好是控制MR中partition分区的,
#然后通过设置reduce的数量,结合分区函数让数据均衡的进入每个reduce即可。
#设置reduce的数量有两种方式,第一种是直接设置reduce个数
set mapreduce.job.reduces=10;
#第二种是设置每个reduce的大小,Hive会根据数据总大小猜测确定一个reduce个数
set hive.exec.reducers.bytes.per.reducer=5120000000; -- 默认是1G,设置为5G
#执行以下语句,将数据均衡的分配到reduce中
set mapreduce.job.reduces=10;
insert overwrite table A partition(dt)
select * from B
distribute by rand();
解释:如设置reduce数量为10,则使用 rand(), 随机生成一个数 x % 10 ,
这样数据就会随机进入 reduce 中,防止出现有的文件过大或过小